温故而知新 回顾JavaScript完美运动框架.

这些天用空余时间回顾和整理了自己刚开始从事前端之时,跟着慕课网上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);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容