js抛物线动画——加入购物车动效
参考文章:http://www.zhangxinxu.com/wordpress/2013/12/javascript-js-元素-抛物线-运动-动画/
parapola.js
1 /*! 2 * by zhangxinxu(.com) 2012-12-27 3 * you can visit http://www.zhangxinxu.com/wordpress/?p=3855 to get more infomation 4 * under MIT license 5 */ 6 var funParabola = function(element, target, options) { 7 /* 8 * 网页模拟现实需要一个比例尺 9 * 如果按照1像素就是1米来算,显然不合适,因为页面动不动就几百像素 10 * 页面上,我们放两个物体,200~800像素之间,我们可以映射为现实世界的2米到8米,也就是100:1 11 * 不过,本方法没有对此有所体现,因此不必在意 12 */ 13 14 var defaults = { 15 speed: 166.67, // 每帧移动的像素大小,每帧(对于大部分显示屏)大约16~17毫秒 16 curvature: 0.001, // 实际指焦点到准线的距离,你可以抽象成曲率,这里模拟扔物体的抛物线,因此是开口向下的 17 progress: function() {}, 18 complete: function() {} 19 }; 20 21 var params = {}; options = options || {}; 22 23 for (var key in defaults) { 24 params[key] = options[key] || defaults[key]; 25 } 26 27 var exports = { 28 mark: function() { return this; }, 29 position: function() { return this; }, 30 move: function() { return this; }, 31 init: function() { return this; } 32 }; 33 34 /* 确定移动的方式 35 * IE6-IE8 是margin位移 36 * IE9+使用transform 37 */ 38 var moveStyle = "margin", testDiv = document.createElement("div"); 39 if ("oninput" in testDiv) { 40 ["", "ms", "webkit"].forEach(function(prefix) { 41 var transform = prefix + (prefix? "T": "t") + "ransform"; 42 if (transform in testDiv.style) { 43 moveStyle = transform; 44 } 45 }); 46 } 47 48 // 根据两点坐标以及曲率确定运动曲线函数(也就是确定a, b的值) 49 /* 公式: y = a*x*x + b*x + c; 50 */ 51 var a = params.curvature, b = 0, c = 0; 52 53 // 是否执行运动的标志量 54 var flagMove = true; 55 56 if (element && target && element.nodeType == 1 && target.nodeType == 1) { 57 var rectElement = {}, rectTarget = {}; 58 59 // 移动元素的中心点位置,目标元素的中心点位置 60 var centerElement = {}, centerTarget = {}; 61 62 // 目标元素的坐标位置 63 var coordElement = {}, coordTarget = {}; 64 65 // 标注当前元素的坐标 66 exports.mark = function() { 67 if (flagMove == false) return this; 68 if (typeof coordElement.x == "undefined") this.position(); 69 element.setAttribute("data-center", [coordElement.x, coordElement.y].join()); 70 target.setAttribute("data-center", [coordTarget.x, coordTarget.y].join()); 71 return this; 72 } 73 74 exports.position = function() { 75 if (flagMove == false) return this; 76 77 var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft, 78 scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 79 80 // 初始位置 81 if (moveStyle == "margin") { 82 element.style.marginLeft = element.style.marginTop = "0px"; 83 } else { 84 element.style[moveStyle] = "translate(0, 0)"; 85 } 86 87 // 四边缘的坐标 88 rectElement = element.getBoundingClientRect(); 89 rectTarget = target.getBoundingClientRect(); 90 91 // 移动元素的中心点坐标 92 centerElement = { 93 x: rectElement.left + (rectElement.right - rectElement.left) / 2 + scrollLeft, 94 y: rectElement.top + (rectElement.bottom - rectElement.top) / 2 + scrollTop 95 }; 96 97 // 目标元素的中心点位置 98 centerTarget = { 99 x: rectTarget.left + (rectTarget.right - rectTarget.left) / 2 + scrollLeft, 100 y: rectTarget.top + (rectTarget.bottom - rectTarget.top) / 2 + scrollTop 101 }; 102 103 // 转换成相对坐标位置 104 coordElement = { 105 x: 0, 106 y: 0 107 }; 108 coordTarget = { 109 x: -1 * (centerElement.x - centerTarget.x), 110 y: -1 * (centerElement.y - centerTarget.y) 111 }; 112 113 /* 114 * 因为经过(0, 0), 因此c = 0 115 * 于是: 116 * y = a * x*x + b*x; 117 * y1 = a * x1*x1 + b*x1; 118 * y2 = a * x2*x2 + b*x2; 119 * 利用第二个坐标: 120 * b = (y2+ a*x2*x2) / x2 121 */ 122 // 于是 123 b = (coordTarget.y - a * coordTarget.x * coordTarget.x) / coordTarget.x; 124 125 return this; 126 }; 127 128 // 按照这个曲线运动 129 exports.move = function() { 130 // 如果曲线运动还没有结束,不再执行新的运动 131 if (flagMove == false) return this; 132 133 var startx = 0, rate = coordTarget.x > 0? 1: -1; 134 135 var step = function() { 136 // 切线 y'=2ax+b 137 var tangent = 2 * a * startx + b; // = y / x 138 // y*y + x*x = speed 139 // (tangent * x)^2 + x*x = speed 140 // x = Math.sqr(speed / (tangent * tangent + 1)); 141 startx = startx + rate * Math.sqrt(params.speed / (tangent * tangent + 1)); 142 143 // 防止过界 144 if ((rate == 1 && startx > coordTarget.x) || (rate == -1 && startx < coordTarget.x)) { 145 startx = coordTarget.x; 146 } 147 var x = startx, y = a * x * x + b * x; 148 149 // 标记当前位置,这里有测试使用的嫌疑,实际使用可以将这一行注释 150 element.setAttribute("data-center", [Math.round(x), Math.round(y)].join()); 151 152 // x, y目前是坐标,需要转换成定位的像素值 153 if (moveStyle == "margin") { 154 element.style.marginLeft = x + "px"; 155 element.style.marginTop = y + "px"; 156 } else { 157 element.style[moveStyle] = "translate("+ [x + "px", y + "px"].join() +")"; 158 } 159 160 if (startx !== coordTarget.x) { 161 params.progress(x, y); 162 window.requestAnimationFrame(step); 163 } else { 164 // 运动结束,回调执行 165 params.complete(); 166 flagMove = true; 167 } 168 }; 169 window.requestAnimationFrame(step); 170 flagMove = false; 171 172 return this; 173 }; 174 175 // 初始化方法 176 exports.init = function() { 177 this.position().mark().move(); 178 }; 179 } 180 181 return exports; 182 }; 183 184 /*! requestAnimationFrame.js 185 * by zhangxinxu 2013-09-30 186 */ 187 (function() { 188 var lastTime = 0; 189 var vendors = ['webkit', 'moz']; 190 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 191 window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 192 window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // name has changed in Webkit 193 window[vendors[x] + 'CancelRequestAnimationFrame']; 194 } 195 196 if (!window.requestAnimationFrame) { 197 window.requestAnimationFrame = function(callback, element) { 198 var currTime = new Date().getTime(); 199 var timeToCall = Math.max(0, 16.7 - (currTime - lastTime)); 200 var id = window.setTimeout(function() { 201 callback(currTime + timeToCall); 202 }, timeToCall); 203 lastTime = currTime + timeToCall; 204 return id; 205 }; 206 } 207 if (!window.cancelAnimationFrame) { 208 window.cancelAnimationFrame = function(id) { 209 clearTimeout(id); 210 }; 211 } 212 }());
使用:
/* 元素 */ var element = document.getElementById("element"), target = document.getElementById("target"); // 抛物线元素的的位置标记 var parabola = funParabola(element, target).mark(); // 抛物线运动的触发 document.body.onclick = function() { element.style.marginLeft = "0px"; element.style.marginTop = "0px"; parabola.init(); };
加入购物车实战:
/* 本demo演示脚本基于ieBetter.js, 项目地址:https://github.com/zhangxinxu/ieBetter.js */ // 元素以及其他一些变量 var eleFlyElement = document.querySelector("#flyItem"), eleShopCart = document.querySelector("#shopCart"); var numberItem = 0; // 抛物线运动 var myParabola = funParabola(eleFlyElement, eleShopCart, { speed: 400, curvature: 0.002, complete: function() { eleFlyElement.style.visibility = "hidden"; eleShopCart.querySelector("span").innerHTML = ++numberItem; } }); // 绑定点击事件 if (eleFlyElement && eleShopCart) { [].slice.call(document.getElementsByClassName("btnCart")).forEach(function(button) { button.addEventListener("click", function() { // 滚动大小 var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || 0, scrollTop = document.documentElement.scrollTop || document.body.scrollTop || 0; eleFlyElement.style.left = event.clientX + scrollLeft + "px"; eleFlyElement.style.top = event.clientY + scrollTop + "px"; eleFlyElement.style.visibility = "visible"; // 需要重定位 myParabola.position().move(); }); }); }