这些天用空余时间回顾和整理了自己刚开始从事前端之时,跟着慕课网上Vivian老师封装的运动框架 move.js.
原视频在 慕课网,下面用markDown写一下具体实现
代码在我的github中有 ouaisheinie.
JavaScript 完美运动框架实现(封装成move.js)
一. 运动框架实现的基本思路和总纲
1.简单动画:left,right,width,height,opacity等基本的属性值或者透明度的匀速运动。
2.缓冲运动。
3.多物体运动。
4.任意值变化。
5.链式运动。
6.同时运动。
二.简单动画
1.匀速动画:
匀速动画满足属性最基本的匀速运动
假设定义一个div1 内部有一个id为share的分享span
<div id="div1">
<span id="share">分享</span>
</div>
样式为:
#div1{
width:200px;
height:200px;
background:red;
position: relative;
left:-200px;
top:0;
}
#div1 span{
width:20px;
height:50px;
background:blue;
position: absolute;
left: 200px;
top:75px;
color:#fff;
}
我们可以用JS操作其在网站上匀速做运动,代码如下:
let oDiv = documet.getElementById("div1");
oDiv.onmouseover = function(){
startMove(0);
}
oDiv.onmouseout = function(){
startMove(-200);
}
let timer = null;
function startMove(iTarget){
clearInterval(timer);//每次执行优先清空定时器
timer = setInterval(()=>{//定时器定义
let speed = 0;
if(oDiv.offsetLeft > iTarget){ //当前left大于目标 就减小
speed = -10;
}else{ //反之增加
speed = 10;
}
if (oDiv.offsetLeft == iTarget){ //到达目标 清除定时器timer
clearInterval(timer);
}else{
oDiv.style.left = oDiv.offsetLeft + speed + "px"; //没到达目标继续改变
}
},30);
}
}
但是仅仅做匀速运动是不够的,接下来我们看看缓冲运动,缓冲运动意思就是在一开始的时候快,但是快要到目标的时候,会慢下来,就像火车到站的时候的速度变化一样。
三.缓冲运动
function startMove(iTarget) {
clearInterval(timer);
timer = setInterval(() => {
let speed = (iTarget - oDiv.offsetLeft) / 10;
//缓冲运动要给速度取整
speed = speed > 0 ? Math.ceil(speed):Math.floor(speed);
if (oDiv.offsetLeft == iTarget) {
clearInterval(timer);
} else {
oDiv.style.left = oDiv.offsetLeft + speed + "px";
}
}, 30);
}
这里的speed不再是10或者-10,而是一个动态值。speed等于目标值与当前值的差的十分之一(具体除以多少可以根据需要改变)。
可以试想一下,最后当目标值与当前值很接近的时候可以无限趋近于0,就会相对静止,运动就结束。但是要注意,这里要给匀速运动判断一下并且取整,speed>0之时用数学方法Math.ceil()向上取整,speed<0之时用Math.floor()向下取整。如果不取整,Javascript会让函数不停执行,分享面板会不停地移动。因为有小数的存在,很难做到oDiv.offsetLeft == iTarget 这个终止条件的达成。
四.多物体运动
试想一下如果给几个元素都添加运动函数。这很简单。
dom结构如下
<ul class="ManyUl">
<li class="manyli"></li>
<li class="manyli"></li>
<li class="manyli"></li>
</ul>
外加样式
.manyli,.ManyUl{
list-style:none;
}
.manyli{
width:200px;
height:100px;
background:purple;
margin-bottom:20px;
}
因为DOM中有三个待添加事件的元素,所以需要遍历给他们添加事件,用到for循环
let aLi = document.getElementsByTagName('li');
for(let i = 0;i<aLi.length;i++){
//给每个Li添加了一个timer属性 给一个空
aLi[i].timer = null;
aLi[i].onmouseover = function(){
startMove(this,400);
}
aLi[i].onmouseout = function(){
startMove(this,200);
}
}
因为给不同的元素添加事件,需要用到this来把事件绑定到某一个元素的上下文环境;所以大家看到这个startMove()里面多了一个参数this。且涉及到给每个元素绑定事件的,不能公用timer。上一节匀速运动的代码中的timer变量就不能定义在外层,必须如上面代码中 每一个aLi都单独定义一个aLi[i].timer = null;只要是多物体运动,所有的变量都不能公用,必须是在私有作用域内。
如果对this的机制不理解的,可以去看看《你不知道的JavaScript》这本书上册的第二部分,74页开始。JS代码如下:
function startMove(obj,iTarget){ //obj是代指被添加事件的元素
clearInterval(obj.timer);
obj.timer = setInterval(function(){
let speed = (iTarget - obj.offsetWidth)/8;
speed = speed > 0?Math.ceil(speed):Math.floor(speed);
if(obj.offsetWidth == iTarget){
clearInterval(obj.timer);
}else{
obj.style.width = obj.offsetWidth + speed + 'px';
}
},30);
}
下面来看一下多物体下,透明度改变的代码。然后再把普通属性以及透明度整合一下。
<div class="div11">
</div>
<div class="div11">
</div>
<div class="div11">
</div>
<div class="div11">
</div>
<div class="div11">
</div>
样式:
.div11{
width:200px;
height:200px;
margin:20px;
float:left;
background:red;
filter:alpha(opacity = 30);
opacity: 0.3;
}
JavaScript代码
window.onload = function(){
let oDiv = document.getElementsByTagName("div");
for(let i = 0;i<oDiv.length;i++){
oDiv[i].timer = null;
oDiv[i].alpha = 30; //多物体运动 alpha 和 timer 不能公用
oDiv[i].onmouseover = function () {
startMove(this,100);
}
oDiv[i].onmouseout = function () {
startMove(this,30);
}
}
//从这里可以看到 之前的timer 和 alpha 变量没有了 因为是5个div的多物体运动 不能公用任何变量
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(()=>{
let speed = 0;
if(obj.alpha > iTarget){
speed = -10;
}else{
speed = 10;
}
if(obj.alpha == iTarget){
clearInterval(obj.timer);
}else{
obj.alpha+=speed;
obj.style.filter = "alpha(opacity =" + obj.alpha +")"; // ie
obj.style.opacity = obj.alpha/100; //火狐或者chrome
}
},30);
}
}
以上就是多物体下 元素透明度的改变的代码 注意在startMove()函数中,透明度改变要写两种表达式,一种是obj.style.filter = "alpha(opacity =" + obj.alpha +")"; 是为了兼容ie浏览器,第二种是obj.style.opacity = obj.alpha/100; 是为了兼容firefox和chrome等浏览器。
五.任意属性变化
顾名思义就是可以让任何属性进行变化,包括边框,填充,边界,透明度等。因为透明度。先来说说透明度,这个要单独拿出来讲。整合上面多物体下的透明度以及一般属性的代码,看一下。dom结构如下:
<ul class="ManyUl">
<li class="manyli2" id="li1"></li>
</ul>
CSS代码如下:
.manyli,.ManyUl{
list-style:none;
}
.manyli{
width:200px;
height:100px;
background:purple;
margin-bottom:20px;
}
下面来看看JS代码:
window.onload = function () {
let li1 = document.querySelector("#li1");
let li2 = document.querySelector("#li2");
li1.onmouseover = function(){
startMove(this,"opacity",100);
};
li1.onmouseout = function(){
startMove(this,"opacity",30);
};
let alpha = 30; //这是一个操作透明度的变量,这个例子暂时没涉及多物体,只是单个元素的改变
function startMove(obj,attr,iTarget) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
let icur = 0;
if(attr == "opacity"){
icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
}else{
icur = parseInt(getStyle(obj, attr));
}
let speed = (iTarget - icur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
if (icur == iTarget) {
clearInterval(obj.timer);
} else {
if(attr == "opacity"){
obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
obj.style.opacity = (icur + speed)/100;
}else{
obj.style[attr] = icur + speed + 'px';
}
}
}, 30);
}
function getStyle(obj,attr){
if(obj.currentStyle){ //ie下
return obj.currentStyle[attr];
}else{//ff 和 chrome 下
return getComputedStyle(obj,false)[attr];
}
}
}
可以看到多了一个函数 getStyle(obj,attr),这是自己封装的一个获取元素属性值的一个函数。兼容ie和firefox,chrome;
startMove()内部我们可以看到定义了一个icur当前值,赋值为0。后面再来改变他。并且在属性非透明度(opacity)的时候,我们会给其用parseInt()函数取整,在这里速度是整数并不容易出错。如果属性是透明度属性,那么属性值肯定会是一个小数,我们用parseFloat()方法取得浮点型数据,然后还需要用数学方法Math.round()四舍五入。才能得到当前的速度。然后再来进行操作。
obj.style[attr] = icur + speed + 'px';这一句中运用[]表示法来表示属性,为的是方便用参数代表要改变的属性。因为opacity属性和其他一般属性的不同之处,所以在判断icur和执行改变状态时都加了一层判断来判断attr是否等于"opacity"。到这里之后,我们就可以封装一个move.js,但是这并不完美,我们还是先封装出来 move.js:
function startMove(obj,attr,iTarget) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
let icur = 0;
if(attr == "opacity"){
icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
}else{
icur = parseInt(getStyle(obj, attr));
}
let speed = (iTarget - icur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
if (icur == iTarget) {
clearInterval(obj.timer);
} else {
if(attr == "opacity"){
obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
obj.style.opacity = (icur + speed)/100;
}else{
obj.style[attr] = icur + speed + 'px';
}
}
}, 30);
}
function getStyle(obj,attr){
if(obj.currentStyle){ //ie下
return obj.currentStyle[attr];
}else{//ff 和 chrome 下
return getComputedStyle(obj,false)[attr];
}
}
}
六.链式运动
链式运动的意思就是执行完一个动作,马上开始执行另一个动作。动作的执行有顺序性和连贯性,不会再同一时间点执行2个或以上任务。html文件中引用一下move.js.上代码:
dom:
<ul>
<li id="li3"></li>
</ul>
CSS:
#li3{
list-style: none;
width:100px;
height:100px;
background:purple;
margin-bottom:20px;
border:4px solid #000;
/* 加个透明度 */
filter:alpha(opacity=30);
opacity: 0.3;
}
运用上面一节我们封装的move.js,这一节的JavaScript代码会非常简单。首先,我们在上一节封装的代码中,并没有第四个参数,回调函数的参数。我们可以修改一下上面封装的move.js,只修改startMove()给其加一个参数fn,而且
function startMove(obj,attr,iTarget,fn) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
let icur = 0;
if(attr == "opacity"){
icur = Math.round(parseFloat(getStyle(obj,attr)) * 100);
}else{
icur = parseInt(getStyle(obj, attr));
}
let speed = (iTarget - icur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
if (icur == iTarget) {
clearInterval(obj.timer);
if(fn){
fn()
}
} else {
if(attr == "opacity"){
obj.style.filter = "alph1(opacity =" + (icur + speed)+")";
obj.style.opacity = (icur + speed)/100;
}else{
obj.style[attr] = icur + speed + 'px';
}
}
}, 30);
}
于是这一节的Js代码
JavaScript:
let Li = document.getElementById("li3");
Li.onmouseover = function(){
startMove(Li,'width',400,function(){
startMove(Li,'height',200,function(){
startMove(Li,"opacity",100);
});
});
}
Li.onmouseout = function(){
startMove(Li,"opacity",30,function(){
startMove(Li,'height',100,function(){
startMove(Li,"width",100);
})
});
}
非常简单,就是用到函数封装,我们执行完第一次运动后马上回调第二次运动的函数,执行完第二次后,马上回调第三次执行的函数,就是这个原理。
七.同时运动
上一节是链式运动,执行完一个动作才能执行另一个动作。那么怎么能让2个或者多个动作同时执行呢。
我们修改一下move.js:
function startMove(obj,json,fn) { //这里用到了Json数据格式,表达出要改变的属性集和成的对象
let flag = true//新增了一个 判断是否停止执行定时器的标准。
clearInterval(obj.timer);
obj.timer = setInterval(function () {
for(let attr in json){ //用for in 循环遍历要改变的属性。定时器每次执行,每个属性都可以改变。
//取当前值
let icur = 0;
if (attr == "opacity") {
icur = Math.round(parseFloat(getStyle(obj, attr)) * 100);
} else {
icur = parseInt(getStyle(obj, attr));
}
//算速度
let speed = (json[attr] - icur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
//停止检测
if (icur != json[attr]) { //还没有执行完 还没到目标值的话,flag为false;
flag = false;
}
if (attr == "opacity") {
obj.style.filter = "alphl(opacity:" + (icur + speed) + ")";
obj.style.opacity = (icur + speed) / 100;
} else {
obj.style[attr] = icur + speed + 'px';
}
}
if(flag){ //flag为true 才执行fn。这里是回调执行的判断。
clearInterval(obj.timer);
if(fn){
fn();
}
}
}, 30);
}
这就是最完美的move.js代码。
同时运动的html结构
<ul>
<li id="li4"></li>
</ul>
CSS代码:
#li4{
list-style: none;
width:100px;
height:100px;
background:purple;
margin-bottom:20px;
border:4px solid #000;
/* 加个透明度 */
filter:alpha(opacity=30);
opacity: 0.3;
}
事先要在html文件中引用move.js.
JavaScript代码:
let oLi = document.getElementById("li4");
oLi.onmouseover = function(){
startMove(oLi,{width:400,height:200,opacity:100});
}
oLi.onmouseout = function(){
startMove(oLi,{width:100,height:100,opacity:30});
}
可以看到,第二个参数是一个json对象,代表你需要同时改变的属性的都有哪些。key为属性,value就是需要改变到的目标值。
在此就整合完毕,move.js最完美的状态可以奉上:
move.js
function getStyle(obj, attr) {
if (obj.currentStyle) { //ie下
return obj.currentStyle[attr];
} else {//ff 和 chrome 下
return getComputedStyle(obj, false)[attr];
}
}
function startMove(obj,json,fn) {
let flag = true//判断是否停止执行定时器的标准
clearInterval(obj.timer);
obj.timer = setInterval(function () {
for(let attr in json){
//取当前值
let icur = 0;
if (attr == "opacity") {
icur = Math.round(parseFloat(getStyle(obj, attr)) * 100);
} else {
icur = parseInt(getStyle(obj, attr));
}
//算速度
let speed = (json[attr] - icur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
//停止检测
if (icur != json[attr]) { //还没有执行完 还没到目标值的话,flag为false;
flag = false;
}
if (attr == "opacity") {
obj.style.filter = "alphl(opacity:" + (icur + speed) + ")";
obj.style.opacity = (icur + speed) / 100;
} else {
obj.style[attr] = icur + speed + 'px';
}
}
if(flag){ //flag为true 才执行fn
clearInterval(obj.timer);
if(fn){
fn();
}
}
}, 30);
}