2017/10/11
【jQuery】放物線を使ったカートインアニメーション
ECサイトでカートに商品を入れた場合に、
カートページへ遷移させずに、
カートに入れたことをアニメーションで知らせるという方法を使ったサイトを見かける機会があると思います。
決済が単品であることがほとんどの場合はカートページへ遷移させたほうがユーザーのストレスが少ないですが、
複数の商品をカートに入れる場合が多いサイトでは、遷移させずにカートに入れたことを知らせるだけにしたほうが、遷移による操作や待機時間を減らせるため、ユーザーのストレス軽減が期待できます。
さらに、遷移させずにカートに入れたことを知らせる場合、Ajaxなどで処理をすることが多いため、通信中の待機時間のストレスを軽減するため、アニメーションが有効です。
カートに入れるアニメーションにもいろいろな種類があると思いますが、
今回は商品の「カートに入れるボタン」から、「ナビゲーションのカートボタン」へボールが投げ入れられるようなイメージのアニメーションを実装する方法を考えて、作ってみました。
↓作ってみたもの
アニメーションの流れ
1. ボタン位置の計算
jQueryを使って、クリックしたボタンと投げ入れる先のボタンの位置を取得します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $(function() { var $target = $(".cart"), //ナビゲーションのカートボタン $window = $(window), targetPos, buttonPos; //カートに入れるボタンを押したら実行 $(document).on("click", ".cartButton__button:not(.is-active)", function() { //「ナビゲーションのカートボタン」と「カートに入れるボタン」の位置を取得 var $self = $(this), 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; ・・・ |
2. 軌道の最初の角度を計算
ボタンの位置を基に、軌道に使う放物線の最初の角度を計算します。
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 | 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(); } ・・・ |
軌道に使う放物線は、初速によっては存在しない軌道になってしまうので、
軌道の存在を確認しながら、初速を調整します。
3. 軌道に沿ってボールをアニメーションさせる
割り出した最初の角度を基に、放物線のアニメーションを実行します。
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 | //アニメーションさせるボールを設置 $("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(); $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); } } |
計算式は、↓こちらのサイトを参考にさせていただきました。
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 | $(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), 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(); $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); } } } } }); }); |
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE