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>
