參考文章: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(); }); }); }

