var funParabola = function(element, target, options) { /* * 网页模拟现实需要一个比例尺 * 如果按照1像素就是1米来算,显然不合适,因为页面动不动就几百像素 * 页面上,我们放两个物体,200~800像素之间,我们可以映射为现实世界的2米到8米,也就是100:1 * 不过,本方法没有对此有所体现,因此不必在意 */ var defaults = { speed: 166.67, // 每帧移动的像素大小,每帧(对于大部分显示屏)大约16~17毫秒 curvature: 0.001, // 实际指焦点到准线的距离,你可以抽象成曲率,这里模拟扔物体的抛物线,因此是开口向下的 progress: function() {}, complete: function() {} }; var params = {}; options = options || {}; for (var key in defaults) { params[key] = options[key] || defaults[key]; } var exports = { mark: function() { return this; }, position: function() { return this; }, move: function() { return this; }, init: function() { return this; } }; /* 确定移动的方式 * IE6-IE8 是margin位移 * IE9+使用transform */ var moveStyle = "margin", testDiv = document.createElement("div"); if ("oninput" in testDiv) { ["", "ms", "webkit"].forEach(function(prefix) { var transform = prefix + (prefix? "T": "t") + "ransform"; if (transform in testDiv.style) { moveStyle = transform; } }); } // 根据两点坐标以及曲率确定运动曲线函数(也就是确定a, b的值) /* 公式: y = a*x*x + b*x + c; */ var a = params.curvature, b = 0, c = 0; // 是否执行运动的标志量 var flagMove = true; if (element && target && element.nodeType == 1 && target.nodeType == 1) { var rectElement = {}, rectTarget = {}; // 移动元素的中心点位置,目标元素的中心点位置 var centerElement = {}, centerTarget = {}; // 目标元素的坐标位置 var coordElement = {}, coordTarget = {}; // 标注当前元素的坐标 exports.mark = function() { if (flagMove === false) return this; if (typeof coordElement.x == "undefined") this.position(); element.setAttribute("data-center", [coordElement.x, coordElement.y].join()); target.setAttribute("data-center", [coordTarget.x, coordTarget.y].join()); return this; } exports.position = function() { if (flagMove === false) return this; var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft, scrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 初始位置 if (moveStyle == "margin") { element.style.marginLeft = element.style.marginTop = "0px"; } else { element.style[moveStyle] = "translate(0, 0)"; } // 四边缘的坐标 rectElement = element.getBoundingClientRect(); rectTarget = target.getBoundingClientRect(); // 移动元素的中心点坐标 centerElement = { x: rectElement.left + (rectElement.right - rectElement.left) / 2 + scrollLeft, y: rectElement.top + (rectElement.bottom - rectElement.top) / 2 + scrollTop }; // 目标元素的中心点位置 centerTarget = { x: rectTarget.left + (rectTarget.right - rectTarget.left) / 2 + scrollLeft, y: rectTarget.top + (rectTarget.bottom - rectTarget.top) / 2 + scrollTop }; // 转换成相对坐标位置 coordElement = { x: 0, y: 0 }; coordTarget = { x: -1 * (centerElement.x - centerTarget.x), y: -1 * (centerElement.y - centerTarget.y) }; /* * 因为经过(0, 0), 因此c = 0 * 于是: * y = a * x*x + b*x; * y1 = a * x1*x1 + b*x1; * y2 = a * x2*x2 + b*x2; * 利用第二个坐标: * b = (y2+ a*x2*x2) / x2 */ // 于是 b = (coordTarget.y - a * coordTarget.x * coordTarget.x) / coordTarget.x; return this; }; // 按照这个曲线运动 exports.move = function() { // 如果曲线运动还没有结束,不再执行新的运动 if (flagMove === false) return this; var startx = 0, rate = coordTarget.x > 0? 1: -1; var step = function() { // 切线 y'=2ax+b var tangent = 2 * a * startx + b; // = y / x // y*y + x*x = speed // (tangent * x)^2 + x*x = speed // x = Math.sqr(speed / (tangent * tangent + 1)); startx = startx + rate * Math.sqrt(params.speed / (tangent * tangent + 1)); // 防止过界 if ((rate == 1 && startx > coordTarget.x) || (rate == -1 && startx < coordTarget.x)) { startx = coordTarget.x; } var x = startx, y = a * x * x + b * x; // 标记当前位置,这里有测试使用的嫌疑,实际使用可以将这一行注释 element.setAttribute("data-center", [Math.round(x), Math.round(y)].join()); // x, y目前是坐标,需要转换成定位的像素值 if (moveStyle == "margin") { element.style.marginLeft = x + "px"; element.style.marginTop = y + "px"; } else { element.style[moveStyle] = "translate("+ [x + "px", y + "px"].join() +")"; } if (startx !== coordTarget.x) { params.progress(x, y); window.requestAnimationFrame(step); } else { // 运动结束,回调执行 params.complete(); flagMove = true; } }; window.requestAnimationFrame(step); flagMove = false; return this; }; // 初始化方法 exports.init = function() { this.position().mark().move(); }; } return exports; } /* 元素 */ 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(); };
/* 元素 */ 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(); }); }); }
学习版本
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" /> <title>CodePen - gwcpwx</title> <style> #goods { cursor: pointer; width: 100px; height: 40px; line-height: 40px; border: 1px solid deeppink; text-align: center; color: deeppink; } #goods:hover { color: #fff; background-color: deeppink; } #cart { position: fixed; right: 0; bottom: 100px; color: deeppink; border: 1px solid deeppink; } </style> <script> </script> </head> <body translate="no" > y = ax² + bx + c <div id="goods"> 商品 </div> <br><br><br><br> <br><br><br><br><br> <br><br><br><br><br> <div id="cart"> 购物车 </div> <script > var goodsDom = document.querySelector("#goods"); var cartDom = document.querySelector("#cart"); goodsDom.onclick = function () { var goodsXLeft = goodsDom.offsetLeft; var goodsYTop = goodsDom.offsetTop; var startX = goodsXLeft + 100; var startY = goodsYTop - document.body.scrollTop + 12; var endX = cartDom.offsetLeft; var endY = cartDom.offsetTop; var diffX = endX - startX; var diffY = endY - startY; // 假设中点(0, 0),也就是方程中的c为0 var c = 0; var a = (endY/endX-startY/startX)/(endX - startX); var b = endY/endX - a*endX; // 创建一个移动的dom var movingDom = document.createElement("div"); movingDom.style.position = 'fixed'; movingDom.style.left = startX + 'px'; movingDom.style.top = startY + 'px'; movingDom.style.height = '16px'; movingDom.style.width = '16px'; movingDom.style.borderRadius = '8px'; movingDom.style.background = 'red'; document.body.appendChild(movingDom) // 定义移动的dom的x, y var x = startX; var y = startY; var ax2 = 0; var bx = 0; var time = setInterval(function(){ if(x < endX) { x = x + 2; ax2 = a*x*x; bx = b*x; y = ax2 + bx; console.log(ax2, bx, y) movingDom.style.left = x + 'px'; movingDom.style.top = y + 'px'; } else { movingDom.parentNode.removeChild(movingDom) clearInterval(time); } },10) } //# sourceURL=pen.js </script> </body> </html>
vue实现抛物线
<template> <div> <ul class="lists"> <li>商品1商品1<i @click="ball_fly($event)">+</i></li> <li>商品12商品1<i @click="ball_fly($event)">+</i></li> <li>商品13商品1<i @click="ball_fly($event)">+</i></li> <li>商品1商品1<i @click="ball_fly($event)">+</i></li> <li>商品12商品1<i @click="ball_fly($event)">+</i></li> <li>商品13商品1<i @click="ball_fly($event)">+</i></li> <li>商品1商品1<i @click="ball_fly($event)">+</i></li> <li>商品12商品1<i @click="ball_fly($event)">+</i></li> <li>商品13商品1<i @click="ball_fly($event)">+</i></li> <li>商品1商品1<i @click="ball_fly($event)">+</i></li> <li>商品12商品1<i @click="ball_fly($event)">+</i></li> <li>商品13商品1<i @click="ball_fly($event)">+</i></li> </ul> <div class="targetbox"><div class="target car_icon" ref="carIcon">购物车</div> </div> </div> </template> <script> export default { name: 'business', data () { return { showMe: false, // 计算商品区域高度 computedContentHeight: window.innerHeight - (window.innerWidth / 10 * 4.2), }; }, methods: { // 初始化 init () { // 给购物车添加animationend事件,动画结束后去掉有animation的class this.$refs.carIcon.addEventListener('animationend', () => { this.$refs.carIcon.classList.remove('tantantan'); }, false); }, // 修改版抛球效果,使用css3中的贝塞尔曲线实现 ball_fly (e) { // 被点元素位置 var bound = e.target.getBoundingClientRect(); var boundTop = bound.top;// 点击top值 var boundLeft = bound.left;// 点击left值 // 目标元素位置 var target = this.$refs.carIcon; var targetData = target.getBoundingClientRect(); var targetTop = targetData.top;// 目标top值 var targetLeft = targetData.left;// 目标left值 // 创建父球(父球横向运动) var father = document.createElement('div'); father.className = 'father flyball'; // 创建子球(子球垂直css3贝塞尔曲线运动,先上后下,得到抛球效果) var child = document.createElement('div'); child.className = 'child inner'; father.appendChild(child); // 设置父盒子生成的位置 // father.style.cssText = 'top:' + boundTop + 'px;left:' + boundLeft + 'px;'; father.style.top = boundTop + 'px'; father.style.left = boundLeft + 'px'; // append小球到页面中 document.body.appendChild(father); setTimeout(() => { // 目标left - 所点元素left + 目标元素宽度的一半(修正落点) father.style.transform = 'translate3d(' + (targetLeft - boundLeft + targetData.width / 2) + 'px, 0px, 0px)'; child.style.cssText = 'transform: translate3d(0px, ' + (targetTop - boundTop) + 'px, 0px);'; // 运动结束后删掉小球 setTimeout(() => { // 移除小球 father.parentNode.removeChild(father); // 购物车添加弹弹弹的css this.$refs.carIcon.classList.add('tantantan'); // 给购物车添加animationend事件,动画结束后去掉有animation的class this.$refs.carIcon.addEventListener('animationend', () => { this.$refs.carIcon.classList.remove('tantantan'); }, false); }, 500); }, 10); } // 生成小球抛出 计算left top 生成动画 不流畅 (css3的没想好) /* ball_fly (e) { // 被点元素宽高 var bound = e.target.getBoundingClientRect(); // 被点元素位置 // 创造元素 var qiu = document.createElement('div'); qiu.className = 'qiu'; qiu.style.top = bound.top + 'px'; qiu.style.left = bound.left + 'px'; document.body.appendChild(qiu); // 目标元素位置 var dsa = this.$refs.carIcon; var mubiao = dsa.getBoundingClientRect(); var mubiaoT = mubiao.top; var mubiaoL = mubiao.left; var timer = null; // top差值 left差值 var chaTop = mubiaoT - bound.top; // 要减掉目标宽度一半 让落点对准目标中心 var chaLeft = bound.left - mubiaoL - dsa.offsetWidth / 2; // 规定上抛初速度为 top 差值的55分之1 var g = chaTop / 55; // 规定上抛初速度为 top 差值的15分之1 var vTop = chaTop / 15; timer = setInterval(() => { qiu.style.top = (qiu.getBoundingClientRect().top + (-vTop + g)) + 'px'; qiu.style.left = (qiu.getBoundingClientRect().left + (-chaLeft / 14)) + 'px'; // 每次 g 对速度的影响 vTop -= g; if (qiu.getBoundingClientRect().top >= mubiaoT) { clearInterval(timer); qiu.parentNode.removeChild(qiu); this.$refs.carIcon.classList.add('tantantan'); } }, 1000 / 25); } */ } }; </script> <style lang="less"> .business_box{ 100%; height:100%; } @keyframes mymove { 0% { transform: scale(1); } 25% { transform: scale(0.8); } 50% { transform: scale(1.1); } 75% { transform: scale(0.9); } 100% { transform: scale(1); } } /* 购物车弹弹弹 */ .tantantan { animation: mymove 1s; } /* 修正版抛球效果所需CSS */ .flyball { position:fixed; top:0; left:0; -webkit-transition:-webkit-transform .5s linear; transition:-webkit-transform .5s linear; transition:transform .5s linear; transition:transform .5s linear, -webkit-transform .5s linear } .flyball .inner { position:absolute; top:0; left:0; background-color:#3190e8; border-radius:50% } .flyball, .flyball .inner { will-change:transform;/* css3自带的开启GPU加速 */ -webkit-transform:translateZ(0); transform:translateZ(0) } .flyball .inner { -webkit-transition:-webkit-transform .5s cubic-bezier(.3, -.2, 1, 0); transition:-webkit-transform .5s cubic-bezier(.3, -.2, 1, 0); transition:transform .5s cubic-bezier(.3, -.2, 1, 0); transition:transform .5s cubic-bezier(.3, -.2, 1, 0), -webkit-transform .5s cubic-bezier(.3, -.2, 1, 0) } /* 父盒子的样式 */ .father{ 20px;; height:20px; position: fixed; z-index: 999; } /* 子盒子(小球)的样式 */ .child{ 20px;; height:20px; background: #3190e8; position: absolute; top: 0; left: 0; } .lists{ 100%; height: auto;; overflow: hidden;} .lists li{ height: 44px; line-height: 40px; background: #f1f1f1; margin-bottom: 20px; position: relative; padding: 0 10px;} .lists li i{ 20px; height: 20px; text-align: center;; line-height: 19px;; display: block; border-radius: 15px; overflow: hidden; position: absolute; right: 20px; top:10px;; background: #3190e8; color: #fff; ;} .targetbox{ position: fixed; bottom: 54px; left: 0; 100%; height: 54px; background: #666;} .target{ 50px; height: 50px; color: #fff; line-height: 50px; text-align: center;} .car_icon { background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zd…48Y2lyY2xlIGN4PSIxMiIgY3k9IjUxIiByPSI0IiBmaWxsPSIjRkZGIi8+PC9nPjwvc3ZnPg==) #3190e8 center no-repeat; border-radius: 50%; background-size: 60% auto; } </style>
css 实现抛物线
<!DOCTYPE html> <html lang="en" style="100%;height:100%;"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <style> * { padding: 0; margin: 0; } #ball { 12px; height:12px; background: #5EA345; border-radius: 50%; position: fixed; transition: left 1s linear, top 1s ease-in; } </style> <title>CSS3 水平抛物线动画</title> </head> <body style="100%;height:100%;"> <div id="ball"></div> </body> <script> var $ball = document.getElementById('ball'); document.body.onclick = function (evt) { console.log(evt.pageX,evt.pageY) $ball.style.top = evt.pageY+'px'; $ball.style.left = evt.pageX+'px'; $ball.style.transition = 'left 0s, top 0s'; setTimeout(()=>{ $ball.style.top = window.innerHeight+'px'; $ball.style.left = '0px'; $ball.style.transition = 'left 1s linear, top 1s ease-in'; }, 20) } </script> </html>
小程序
cartAnimation(x, y) { // x y 为手指点击的坐标,即球的起始坐标 let self = this, cartY = app.globalData.winHeight - 50, // 结束位置(购物车图片)纵坐标 cartX = 50, // 结束位置(购物车图片)的横坐标 animationX = flyX(cartX, x), // 创建球的横向动画 animationY = flyY(cartY, y) // 创建球的纵向动画 this.setData({ ballX: x, ballY: y, showBall: true }) setTimeoutES6(100).then(() => { // 100 ms 延时,确保球已经到位并显示 self.setData({ animationX: animationX.export(), animationY: animationY.export(), }) return setTimeoutES6(400) // 400 ms 是球的抛物线动画时长 }).then(() => { // 400 ms 延时后隐藏球 this.setData({ showBall: false, }) }) } function setTimeoutES6(sec) { // Promise 化 setTimeout return new Promise((resolve, reject) => { setTimeout(() => {resolve()}, sec) }) } function flyX(cartX, oriX) { // 水平动画 let animation = wx.createAnimation({ duration: 400, timingFunction: 'linear', }) animation.left(cartX).step() return animation } function flyY(cartY, oriY) { // 垂直动画 let animation = wx.createAnimation({ duration: 400, timingFunction: 'ease-in', }) animation.top(cartY).step() return animation }
222
var funParabola = function(element, target, options) { /* * 网页模拟现实需要一个比例尺 * 如果按照1像素就是1米来算,显然不合适,因为页面动不动就几百像素 * 页面上,我们放两个物体,200~800像素之间,我们可以映射为现实世界的2米到8米,也就是100:1 * 不过,本方法没有对此有所体现,因此不必在意 */ var defaults = { speed: 166.67, // 每帧移动的像素大小,每帧(对于大部分显示屏)大约16~17毫秒 curvature: 0.001, // 实际指焦点到准线的距离,你可以抽象成曲率,这里模拟扔物体的抛物线,因此是开口向下的 progress: function() {}, complete: function() {} }; var params = {}; options = options || {}; for (var key in defaults) { params[key] = options[key] || defaults[key]; } var exports = { mark: function() { return this; }, position: function() { return this; }, move: function() { return this; }, init: function() { return this; } }; /* 确定移动的方式 * IE6-IE8 是margin位移 * IE9+使用transform */ var moveStyle = "margin", testDiv = document.createElement("div"); if ("oninput" in testDiv) { ["", "ms", "webkit"].forEach(function(prefix) { var transform = prefix + (prefix? "T": "t") + "ransform"; if (transform in testDiv.style) { moveStyle = transform; } }); } // 根据两点坐标以及曲率确定运动曲线函数(也就是确定a, b的值) /* 公式: y = a*x*x + b*x + c; */ var a = params.curvature, b = 0, c = 0; // 是否执行运动的标志量 var flagMove = true; if (element && target && element.nodeType == 1 && target.nodeType == 1) { var rectElement = {}, rectTarget = {}; // 移动元素的中心点位置,目标元素的中心点位置 var centerElement = {}, centerTarget = {}; // 目标元素的坐标位置 var coordElement = {}, coordTarget = {}; // 标注当前元素的坐标 exports.mark = function() { if (flagMove === false) return this; if (typeof coordElement.x == "undefined") this.position(); element.setAttribute("data-center", [coordElement.x, coordElement.y].join()); target.setAttribute("data-center", [coordTarget.x, coordTarget.y].join()); return this; } exports.position = function() { if (flagMove === false) return this; var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft, scrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 初始位置 if (moveStyle == "margin") { element.style.marginLeft = element.style.marginTop = "0px"; } else { element.style[moveStyle] = "translate(0, 0)"; } // 四边缘的坐标 rectElement = element.getBoundingClientRect(); rectTarget = target.getBoundingClientRect(); // 移动元素的中心点坐标 centerElement = { x: rectElement.left + (rectElement.right - rectElement.left) / 2 + scrollLeft, y: rectElement.top + (rectElement.bottom - rectElement.top) / 2 + scrollTop }; // 目标元素的中心点位置 centerTarget = { x: rectTarget.left + (rectTarget.right - rectTarget.left) / 2 + scrollLeft, y: rectTarget.top + (rectTarget.bottom - rectTarget.top) / 2 + scrollTop }; // 转换成相对坐标位置 coordElement = { x: 0, y: 0 }; coordTarget = { x: -1 * (centerElement.x - centerTarget.x), y: -1 * (centerElement.y - centerTarget.y) }; /* * 因为经过(0, 0), 因此c = 0 * 于是: * y = a * x*x + b*x; * y1 = a * x1*x1 + b*x1; * y2 = a * x2*x2 + b*x2; * 利用第二个坐标: * b = (y2+ a*x2*x2) / x2 */ // 于是 b = (coordTarget.y - a * coordTarget.x * coordTarget.x) / coordTarget.x; return this; }; // 按照这个曲线运动 exports.move = function() { // 如果曲线运动还没有结束,不再执行新的运动 if (flagMove === false) return this; var startx = 0, rate = coordTarget.x > 0? 1: -1; var step = function() { // 切线 y'=2ax+b var tangent = 2 * a * startx + b; // = y / x // y*y + x*x = speed // (tangent * x)^2 + x*x = speed // x = Math.sqr(speed / (tangent * tangent + 1)); startx = startx + rate * Math.sqrt(params.speed / (tangent * tangent + 1)); // 防止过界 if ((rate == 1 && startx > coordTarget.x) || (rate == -1 && startx < coordTarget.x)) { startx = coordTarget.x; } var x = startx, y = a * x * x + b * x; // 标记当前位置,这里有测试使用的嫌疑,实际使用可以将这一行注释 element.setAttribute("data-center", [Math.round(x), Math.round(y)].join()); // x, y目前是坐标,需要转换成定位的像素值 if (moveStyle == "margin") { element.style.marginLeft = x + "px"; element.style.marginTop = y + "px"; } else { element.style[moveStyle] = "translate("+ [x + "px", y + "px"].join() +")"; } if (startx !== coordTarget.x) { params.progress(x, y); window.requestAnimationFrame(step); } else { // 运动结束,回调执行 params.complete(); flagMove = true; } }; window.requestAnimationFrame(step); flagMove = false; return this; }; // 初始化方法 exports.init = function() { this.position().mark().move(); }; } return exports; } /* 元素 */ 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(); };
333
<!doctype html > <html> <head> <meta charset="utf-8"/> <title>抛物线运动</title> <style> .pwx_rect{position:absolute;left:10px;top:300px;background-color:#888;height:50px;50px;} .pwx_hr{border-top:2px solid #ddd;position:absolute;98%;left:0px;top:350px;} </style> <script> test = function(){ var rect = document.getElementById("rect"); pwx(rect,60,5); //参数2:抛物线角度,参数3:横向速度每次增加5 } function pwx(rect,radian,step){ var animate = function(opt){ var cos = Math.cos(opt.radian*Math.PI/180);//邻边比斜边,60度的话等于1/2 var sin = Math.sin(opt.radian*Math.PI/180);//对边比斜边,30度的话等于1/2 var left = opt.rect.offsetLeft; var top = opt.rect.offsetTop; if(opt.radian>0){ left+=opt.step; opt.radian-=1; //角度递减1 var a = left - opt.initLeft; var c = (a/cos); var b = (sin*c); opt.rect.style.left = opt.initLeft+a+"px"; opt.rect.style.top = opt.initTop-b+"px"; setTimeout(function(){ animate(opt); },10); }else{ opt.rect.style.left = left+opt.step+"px"; opt.rect.style.top = opt.initTop+"px"; } } animate({ step : step, rect : rect, radian : radian, initTop : rect.offsetTop, initLeft : rect.offsetLeft }); } </script> </head> <body> <input type="button" value="抛物线" onclick="test()"/> <div class="pwx_rect" id="rect"></div> <div class="pwx_hr"></div> </body> </html>
/*! * parabola trajectory v1.0 * * Contact: https://github.com/xiaolin3303 * 2016-09-30 * * Designed and built with all the love of Web */ ;(function (window, Math) { /* * @params Object opts */ function Parabola (opts) { opts = opts || {}; // required `startPos`, `endPos` params in opts if (!opts.startPos) { throw new Error('`startPos` is required in init options'); } if (!opts.endPos) { throw new Error('`endPos` is required in init options'); } // opts.curvature = opts.curvature || 0.003; opts.duration = opts.duration || 2000; this.opts = opts; this.calCurvature(); } Parabola.prototype.calCurvature = function () { this.opts.driftX = this.opts.endPos.left - this.opts.startPos.left; this.opts.driftY = this.opts.endPos.top - this.opts.startPos.top; // 在不超出屏幕范围的前提下,尽量抛得更高,计算合适的曲率 (a) var yMin = -1 * this.opts.startPos.top; var a = this.power(this.opts.driftX, 4); var b = (4 * yMin - 2 * this.opts.driftY) * this.power(this.opts.driftX, 2); var c = this.power(this.opts.driftY, 2); this.opts.curvature = (-1 * b + Math.sqrt((this.power(b, 2) - 4 * a * c))) / (2 * a); this.opts.b = (this.opts.driftY - this.opts.curvature * this.opts.driftX * this.opts.driftX) / this.opts.driftX; } Parabola.prototype.power = function (v, n) { if (n === 1) { return v; } else { return v * arguments.callee(v, (n - 1)); } } Parabola.prototype.calPosition = function (progress) { // 当前进度下的X轴的位置 x = this.opts.driftX * progress; // 当前进度下的Y轴的位置 // y = a*x*x + b*x + c, c = 0 y = this.opts.curvature * x * x + this.opts.b * x; return { left: Math.round(x + this.opts.startPos.left), top: Math.round(y + this.opts.startPos.top) } } Parabola.prototype.start = function () { var opts = this.opts; var me = this; var startTimeStamp = +new Date(); var animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; function step () { var currentTimeStamp = +new Date(); var progress = Math.min((currentTimeStamp - startTimeStamp) / opts.duration, 1); if (progress === 1) { // 动画结束 return false; } else { var position = me.calPosition(progress); opts.onStep && opts.onStep(position); return true; } } function progress () { if (step()) { animationFrame(progress); } else { if (typeof opts.onFinish === 'function') { opts.onFinish(opts.endPos); } } } animationFrame(progress); } if ( typeof module !== 'undefined' && module.exports ) { module.exports = Parabola; } else if ( typeof define === 'function' && define.amd ) { define( function () { return Parabola; } ); } else { window.Parabola = Parabola; } })(window, Math) window.onload = function () { var btn = document.querySelector('button'); var target = document.querySelector('.dot'); var parabola = new Parabola({ startPos: { left: 100, top: 60 }, endPos: { left: 500, top: 200 }, duration: 1000, onStep (pos) { target.style.left = pos.left + 'px'; target.style.top = pos.top + 'px'; }, onFinish (pos) { target.classList.add('scaleAnimation'); console.log('Animation Finished!'); } }); // parabola.start(); btn.addEventListener('click', function () { target.classList.remove('scaleAnimation'); parabola.start(); }, false); }
fly.js
/* * jquery.fly * * 抛物线动画 * @github https://github.com/amibug/fly * Copyright (c) 2014 wuyuedong * copy from tmall.com */ (function ($) { $.fly = function (element, options) { // 默认值 var defaults = { version: '1.0.0', autoPlay: true, vertex_Rtop: 20, // 默认顶点高度top值 speed: 1.2, start: {}, // top, left, width, height end: {}, onEnd: $.noop }; var self = this, $element = $(element); /** * 初始化组件,new的时候即调用 */ self.init = function (options) { this.setOptions(options); !!this.settings.autoPlay && this.play(); }; /** * 设置组件参数 */ self.setOptions = function (options) { this.settings = $.extend(true, {}, defaults, options); var settings = this.settings, start = settings.start, end = settings.end; $element.css({marginTop: '0px', marginLeft: '0px', position: 'fixed'}).appendTo('body'); // 运动过程中有改变大小 if (end.width != null && end.height != null) { $.extend(true, start, { $element.width(), height: $element.height() }); } // 运动轨迹最高点top值 var vertex_top = Math.min(start.top, end.top) - Math.abs(start.left - end.left) / 3; if (vertex_top < settings.vertex_Rtop) { // 可能出现起点或者终点就是运动曲线顶点的情况 vertex_top = Math.min(settings.vertex_Rtop, Math.min(start.top, end.top)); } /** * ====================================================== * 运动轨迹在页面中的top值可以抽象成函数 y = a * x*x + b; * a = curvature * b = vertex_top * ====================================================== */ var distance = Math.sqrt(Math.pow(start.top - end.top, 2) + Math.pow(start.left - end.left, 2)), // 元素移动次数 steps = Math.ceil(Math.min(Math.max(Math.log(distance) / 0.05 - 75, 30), 100) / settings.speed), ratio = start.top == vertex_top ? 0 : -Math.sqrt((end.top - vertex_top) / (start.top - vertex_top)), vertex_left = (ratio * start.left - end.left) / (ratio - 1), // 特殊情况,出现顶点left==终点left,将曲率设置为0,做直线运动。 curvature = end.left == vertex_left ? 0 : (end.top - vertex_top) / Math.pow(end.left - vertex_left, 2); $.extend(true, settings, { count: -1, // 每次重置为-1 steps: steps, vertex_left: vertex_left, vertex_top: vertex_top, curvature: curvature }); }; /** * 开始运动,可自己调用 */ self.play = function () { this.move(); }; /** * 按step运动 */ self.move = function () { var settings = this.settings, start = settings.start, count = settings.count, steps = settings.steps, end = settings.end; // 计算left top值 var left = start.left + (end.left - start.left) * count / steps, top = settings.curvature == 0 ? start.top + (end.top - start.top) * count / steps : settings.curvature * Math.pow(left - settings.vertex_left, 2) + settings.vertex_top; // 运动过程中有改变大小 if (end.width != null && end.height != null) { var i = steps / 2, width = end.width - (end.width - start.width) * Math.cos(count < i ? 0 : (count - i) / (steps - i) * Math.PI / 2), height = end.height - (end.height - start.height) * Math.cos(count < i ? 0 : (count - i) / (steps - i) * Math.PI / 2); $element.css({ width + "px", height: height + "px", "font-size": Math.min(width, height) + "px"}); } $element.css({ left: left + "px", top: top + "px" }); settings.count++; // 定时任务 var time = window.requestAnimationFrame($.proxy(this.move, this)); if (count == steps) { window.cancelAnimationFrame(time); // fire callback settings.onEnd.apply(this); } }; /** * 销毁 */ self.destroy = function(){ $element.remove(); }; self.init(options); }; // add the plugin to the jQuery.fn object $.fn.fly = function (options) { return this.each(function () { if (undefined == $(this).data('fly')) { $(this).data('fly', new $.fly(this, options)); } }); }; })(jQuery);
<script> $(function() { var offset = $("#end").offset(); $(".addcar").click(function(event){ var addcar = $(this); var img = addcar.parent().find('img').attr('src'); var flyer = $('<img class="u-flyer" src="'+img+'">'); flyer.fly({ start: { left: event.pageX, top: event.pageY }, end: { left: offset.left+10, top: offset.top+10, 0, height: 0 }, onEnd: function(){ $("#msg").show().animate({ '250px'}, 200).fadeOut(1000); addcar.css("cursor","default").removeClass('orange').unbind('click'); this.destory(); } }); }); }); </script>