2019/04/09
【jQuery】LaunchCartでAjaxによるカートイン
スターフィールドのブログで、以前カートインのアニメーションについて紹介させていただきました。このようにページ遷移させずにカートインさせる場合、実際にはAjaxによる処理を行う必要があります。
そこで今回は、LaunchCart上でAjaxによるカートインを実装する方法についてご紹介いたします。
方法
- カートインアニメーションのJavaScriptを用意
- LaunchCartのカートのテンプレートページでAjaxの処理を記述
- 各テンプレートの商品のカートインボタンにカートインのフォームを設置
- JavaScriptによるカートインアニメーションにAjaxの処理を追加
1. カートインアニメーションのJavaScriptを用意
今回は以前紹介したカートインアニメーションをLaunchCartに設置する場合を想定します。
※ Twigによりシステムから情報を動的に受け取る必要があるため、このJavaScriptのコードはLaunchCartのテンプレート上に記述する必要があります。
2. LaunchCartのカートのテンプレートページでAjaxの処理を記述
LaunchCart管理画面で「デザイン」 > 「システムテンプレート」 > 「カート」でカートのテンプレートページに入ります。
ここで、Ajaxでカートページにアクセスした場合は、JSON形式で返すように場合分けします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | {# リクエストヘッダーのHTTP_X_REQUESTED_WITHプロパティを参照し、Ajaxによる通信かどうかを判定します #} {% set http_x = app.request.server.get('HTTP_X_REQUESTED_WITH') %} {# Ajaxによるアクセスの場合はJSON形式でカートの情報を返す #} {% if http_x == 'XMLHttpRequest' %} {# カートインに成功した場合 #} {% if cart_api.count %} { "status": "ok", "cart_item": {{ cart_api.count }}, "cart_price": "{{ unitFront }}{{ (curRatio * cart_api.total)|price_format(decimalPoint) }}{{ unitBack }}" } {# カートインに失敗した場合の処理 #} {% else %} { "status": "no", "message": "Sorry, failed cart in." } {% endif %} {# Ajaxによる通信でない場合は、通常のカートページを表示する #} {% else %} {# カートページのテンプレートをここに記述 #} {% endif %} |
ポイントは、
{% set http_x = app.request.server.get(‘HTTP_X_REQUESTED_WITH’) %}
でリクエストヘッダのHTTP_X_REQUESTED_WITHプロパティを参照し、Ajaxかどうかを判定することです。
3. 各テンプレートの商品のカートインボタンにカートインのフォームを設置
各テンプレートの商品のカートインボタンに、通常のページ遷移と同様のフォームを設置します。
1 2 3 4 5 | <form action="{{ path("ec_client_cart_add", {"product_id":product.id})}}" method="post"> <p class="cartButton"><button type="button" class="cartButton__button">カートに入れる</button></p> <input type="hidden" name="product_sku_id" value="{{ productskus[0].id }}"> <input type="hidden" name="quantity" value="1"> </form> |
4. JavaScriptによるカートインアニメーションにAjaxの処理を追加
いよいよカートインアニメーションにAjaxの処理を追加します。
今回のアニメーションでは、まずカートインボタンをクリックしたタイミングで、Ajaxの処理を開始するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //カートに入れるボタンを押したら実行 $(document).on("click", ".cartButton__button:not(.is-active)", function() { var $form = $(this).closest('form'); var params = $form.serialize(); $.ajax({ type: 'POST', url: '{{ path("ec_client_cart_add", {"product_id":product.id}) }}', data: params, dataType: 'json' }).done(function (data) { if(data.status == 'ok') { // カートインに成功した場合の処理 ・・・ } else { // カートインに失敗した場合の処理 console.log('Error: ' + data.message); } }).fail(function ( jqXHR, textStatus, errorThrown) { // カートインに失敗した場合の処理 console.log(textStatus + ': ' + errorThrown); }) |
元のカートインアニメーションの処理では、アニメーションが終わったタイミングでダミーでカートイン成功のアニメーションやカートの数を変更する処理を行っていましたが、
カートイン成功のアニメーションとカートの数を変更する処理を、Ajaxでのカートインに成功したタイミングで、Ajaxで返ってきた情報をもとに行うように変更します。
アニメーション終了処理の記述を変更
1 2 3 4 5 6 7 8 9 10 11 12 | //アニメーションの終了処理 $ball.remove(); //↓ここから $target.children() .addClass("is-active"); $cartNum.text(Number($cartNum.text()) + 1); $self.text("カートに入れました"); var timer = setTimeout(function() { clearInterval(timer); $target.children().removeClass("is-active"); }, 1000); // ↑ここまでを削除 |
Ajaxに成功した場合の処理を記述する箇所に、先程アニメーションの終了処理で削除した処理を一部変更して移植
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ・・・ }).done(function (data) { if(data.status == 'ok') { // カートインに成功した場合の処理 $target.children() .addClass("is-active"); $cartNum.text(data.cart_item); //←Ajaxで返ってきた情報に変更 $self.text("カートに入れました"); var timer = setTimeout(function() { clearInterval(timer); $target.children().removeClass("is-active"); }, 1000); } else { // カートインに失敗した場合の処理 console.log('Error: ' + data.message); } }).fail(function ( jqXHR, textStatus, errorThrown) { ・・・ |
Ajaxでの通信の場合にJSON形式のデータを返す方法は他のシステムページでの処理にも応用でき、LaunchCartでの表現の幅を大きく広げることが可能ですので、ぜひ試してみてください。
今回のカートインアニメーションのJavaScript全文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | $(function() { var $target = $(".cart"), //ナビゲーションのカートボタン $cartNum = $target.find(".cartButton__num"), //カートに入れるボタン $window = $(window), targetPos, buttonPos; //カートに入れるボタンを押したら実行 $(document).on("click", ".cartButton__button:not(.is-active)", function() { var $self = $(this); var $form = $self.closest('form'); var params = $form.serialize(); $.ajax({ type: 'POST', url: '{{ path("ec_client_cart_add", {"product_id":product.id}) }}', data: params, dataType: 'json' }).done(function (data) { if ($cart_box.children('.badge').length) { $target.children() .addClass("is-active"); $cartNum.text(data.cart_item); $self.text("カートに入れました"); var timer = setTimeout(function() { clearInterval(timer); $target.children().removeClass("is-active"); }, 1000); } else { $cart_box.append('<span class="badge">' + data.cart_item + '</span>'); } }).fail(function ( jqXHR, textStatus, errorThrown) { console.log(textStatus + ': ' + errorThrown); }) //「ナビゲーションのカートボタン」と「カートに入れるボタン」の位置を取得 var targetPos = $target.offset(), buttonPos = $self.offset(); targetPos.top = targetPos.top - $window.scrollTop(); buttonPos.top = buttonPos.top - $window.scrollTop(); targetPos.left = targetPos.left + $target.width() / 2; buttonPos.left = buttonPos.left + $self.width() / 2; var G = 10, //重力[px/s^2] v = 0, //初速[px/s] H = buttonPos.top - targetPos.top, //高度[px] S = targetPos.left - buttonPos.left; //距離[px] calcOrbit(); /** * 放物線の軌道を計算 */ function calcOrbit() { //解の存在の有無判定に使う値 var b = -1 * (2 * v * v * S) / (G * S * S), c = 1 + (2 * v * v * H) / (G * S * S); var D = b * b - 4 * c; //0以上なら解が存在 if(D >= 0) { //解が存在する場合はアニメーションを実行 //放物線の最初の角度を算出 var tanTheta0 = Math.atan((-b - Math.sqrt(D)) / 2), tanTheta1 = Math.atan((-b + Math.sqrt(D)) / 2), theta = Math.max(tanTheta0, tanTheta1); //アニメーションを実行 startAnimation(); } else { //解が存在しない場合は、初速を追加して放物線の軌道を再計算 v++; calcOrbit(); } /** * アニメーションを実行 */ function startAnimation() { //アニメーションさせるボールを設置 $("body").append('<div class="ball" />'); var $ball = $(".ball").last(); $ball.css({ "left": buttonPos.left + "px", "top": buttonPos.top + "px" }); $self .addClass("is-active") .text("処理中"); //アニメーション実装 var startTime = performance.now(); requestAnimationFrame(loop); function loop(nowTime) { var t = (nowTime - startTime) / 75, //時間を取得 x = v * Math.cos(theta) * t, //横方向の位置を算出 y = Math.tan(theta) * x - (G / (2 * v * v * Math.cos(theta) * Math.cos(theta))) * x * x; //縦方向の位置を算出 //算出した値を基にボールの位置を移動 $ball.css({ "left": Math.round(buttonPos.left + x) + "px", "top": Math.round(buttonPos.top - y) + "px" }); //ボールがカートボタンに到着するまでアニメーションを実行 if(buttonPos.left + x < targetPos.left) { requestAnimationFrame(loop); } else { //アニメーションの終了処理 $ball.remove(); } } } } }); }); |
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE