原生JavaScript运动功能系列(三):多物体多值运动

  • 多物体同时出发运动函数实现
  • 多属性同步运动变化实现

一、多物同时触发运动函数实现

前面两个动画示例基本理解了动画的核心:位置变化和速度变化,操作的核心就是定时器分段叠加属性值。但是动画还是基于单个元素实现,如果将前面封装的动画实现方法同时触发我可以肯定的告诉你会有bug,我们先来写一个示例看看这个bug是什么?示例需求是有三个div同时居于浏览器左侧,当鼠标进入div时,当前div的宽度变宽,鼠标离开时,宽度也已动画状态恢复。

//html
<div></div>
<div></div>
<div></div>

//css
div{
    width: 100px;
    height: 50px;
    margin-bottom: 10px;
    background-color: red;
    border: 1px solid #000;
}

还是基于昨天的缓冲运动来做,只是将变化样式从相对浏览器位置换成元素宽度,另外因为样式变化,考虑后期也会经常需要获取不同的样式需要,封装了一个获取样式的方法getStyle();这个段代码是为了引出从单个触发动画到多个触发动画函数产生的bug,代码存在bug,也是为后面的内容做铺垫。

 1 var divArr = document.getElementsByTagName("div");
 2 var timer = null;
 3 for(var i = 0; i < divArr.length; i++){
 4     divArr[i].onmouseover = function(){
 5         startMove(this,400,7);
 6     }
 7     divArr[i].onmouseout = function(){
 8         startMove(this,100,7);
 9     }
10 }
11 function getStyle(dom,attr){
12     if(dom.currentStyle){
13
14         return dom.currentStyle[attr];
15     }else{
16         return window.getComputedStyle(dom,false)[attr];
17     }
18 }
19 function startMove(dom,target,divisor){
20     clearInterval(timer);
21     var iSpeed,iCur;
22     timer = setInterval(function (){
23         iCur = parseInt(getStyle(dom,"width"));
24         iSpeed = (target-iCur)/divisor;
25         //console.log(iSpeed+"..."+Math.ceil(iSpeed));
26         iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
27         if(iCur === target){
28             clearInterval(timer);
29         }else{
30             dom.style.width=iCur+iSpeed+‘px‘;
31         }
32     },30);
33 }

这段代码出现的问题就是,当触发一个div的动画效果,动画还没有运行到终点时,又触发了另一个div的动画,这时前一个动画就会停在当前位置。就像下面这个效果(测试bug效果)。

只有最后一个元素运动到了终点,其实这个bug很明显,从单个元素运动到多个元素运动,首先要看是单点触发动画,还是多点触发动画,上面这个示例就是多点触发动画,多点触发动画就必然需要每个触发点都有自己的一个定时器来执行属于自己的动画程序。那单点触发动画会出现在什么情况呢?开篇的任务列表中有一个链式运动,就是一个触发点接着就是一个元素接着一个元素运动,这种情况就可以使用单点触发,但是为了运动函数的扩展性,一般都采用多点触发封装,在实际开发中,动画函数肯定是被多点触发的。

实质上这个bug是由闭包产生的,因为在上面的运动函数中的定时器,是由写在动画函数执行前的timer变量统一管理,当另一个动画开始执行时,会通过作用域链上的timer将之前的定时器关闭,并在这个变量上赋值自己的定时器来执行自己的动画,解决这个问题的方法很简单就是给每个执行动画拥有自己的timer来单独管理自己的定时器,就不会出现闭包冲突bug了。这个修改很简单,就是给每个触发运动的DOM对象添加一个timer属性来管理自己的定时器就可以了。修改方案如下:

//20、22、28的timer修改成dom.timer//第二行的var timer = null;删除

二、多属性同步运动变化实现

在前面的所有示例中,我们都是在操作单一样式运动,怎么操作多个样式同步运动变化呢?其实在上一个示例中,我已经埋下了伏笔,这个伏笔就是获取对象样式的方法getStyle(dom,attr)。其实理论上很简单,我们只需要将之前传入的样式终点参数,改成多个样式的终点参数对象就可以了,然后在每次定时器执行时,循环这个对象然后对每个样式进行修改就可以了,这里不讨论js单线程,每个样式修改都是存在先后顺序的,因为计算机的执行速度是以毫秒级速度执行,人眼识别可以忽略这种先后顺序。由于这个功能从功能逻辑上来讲很简单,但是实际处理过程中还是会有些需要注意的细节,而且这些细节有时候会对你的程序造成致命的打击,在没有实现代码之前不太容易用语言表达清楚,所以先上代码,然后再来就着代码剖析具体问题:

 1 //css
 2 div{
 3     position: absolute;
 4     left: 0;
 5     width: 100px;
 6     height: 50px;
 7     margin-bottom: 10px;
 8     background-color: red;
 9     border: 1px solid #000;
10 }
11 //html
12 <div class="demo"></div>
13
14 //js
15 var demoDiv = document.getElementsByClassName("demo")[0];
16 var targetObj = {
17     width: 400,
18     height: 300,
19     opacity: 50,
20     left: 150,
21     top: 150
22 }
23 demoDiv.onclick = function(){
24     startMove(this,targetObj,7);
25 }
26 function getStyle(dom,attr){
27     if(dom.currentStyle){
28         return dom.currentStyle[attr];
29     }else{
30         return window.getComputedStyle(dom,false)[attr];
31     }
32 }
33 function startMove(dom,json,divisor){
34     clearInterval(dom.timer);
35     var iSpeed,iCur;
36     dom.timer = setInterval(function (){
37         var bStop = true;
38         for(var attr in json){
39             if(attr == "opacity"){
40                 iCur = Math.round( parseFloat( getStyle(dom,attr) ) * 100);
41             }else{
42                 iCur = parseInt( getStyle(dom,attr) );
43             }
44             iSpeed = (json[attr]-iCur)/divisor;
45             iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
46             if(attr == "opacity"){
47                 dom.style.opacity = (iCur + iSpeed)/100;
48             }else{
49                 dom.style[attr] = iCur+iSpeed+‘px‘;
50             }
51             if( iCur != json[attr]){
52                 bStop = false;
53             }
54         }
55         if(bStop){
56             clearInterval(dom.timer);
57         }
58     },30);
59 }

一开始看到这个方法估计会有点懵,大神略过,下面我就来将这个方法的全部内容逐个剖析:

  • 39~43:获取变量==>整数值变量与浮点数变量的操作差异;
  • 44~45:计算单次运动距离;
  • 46~50:修改元素样式值;
  • 51~53:判断元素的所有样式是否都到达终点;
  • 55~57:确认样式都到达终点后,结束定时器

需要重点剖析的是获取变量和变量赋值,以及什么时候结束定时器的实际业务逻辑问题。由于opacity的值是0~1,所以获取样式后转成浮点数值这个我相信都可以理解,然后放大100倍是因为考虑到需要计算移动距离,并且可以和其他值一样使用同一的逻辑计算(匹配45行代码),那为什么在乘以100的时候需要做四舍五入(Math.round)的操作呢?如果对js的数值标准IEEE_754浮点数计算标准有所了解就能明白,不明白也没关系,我来给大家解释一下,这是因为js的浮点数计算不精确造成的,比如出现(0.58*100!=58),控制台打印结果是:57.99999999999999,还有比如0.1+0.2!=0.3;太深入的原理就不在这里讲了,需要在这个地方使用Math.round四舍五入的原因就是精度丢失造成的,但是这个差值非常的小,所以可以使用四舍五入的方式获得我们想要的精确的数值,如果这个地方不使用,可能会出现死循环。(这里测试IE11可以没问题,在IE7环境下测试就会出现死循环)。

然后什么时候结束定时器执行这个问题是因为我们不知道再调用方法时会要修改多少样式,而因为运动实际上不会同时到达终点,所以需要判断每个样式都到达终点后结束定时器。

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/10387418.html

时间: 2024-08-07 15:18:15

原生JavaScript运动功能系列(三):多物体多值运动的相关文章

原生JavaScript运动功能系列(五):定时定点运动

原生JavaScript运动功能系列(一):运动功能剖析与匀速运动实现 原生JavaScript运动功能系列(二):缓冲运动 原生JavaScript运动功能系列(三):多物体多值运动 原生JavaScript运动功能系列(四):多物体多值链式运动 这篇博客剖析一个问题,就是怎么实现将元素指定时间运动到目标位置?前面的博客都是在处理运动行为,没有对运动时间做任何限定,只是因为清晰的分析运动行为和实现原理,要想一个动画函数具备健全的功能,并且可以随意使用,通过参数设定动画执行时间是非常有必要的一个

Javascript模块化编程系列三: CommonJS &amp; AMD 模块化规范描述

CommonJS Module 规范 CommonJS 的模块化规范描述在Modules/1.1.1 中 目前实现此规格的包有: Yabble,CouchDB,Narwhal (0.2), Wakanda, TeaJS (formerly v8cgi), CommonScript, PINF JS Loader, SeaJS, ArangoDB, sorrow.js 注意,这里并没有找到 requireJS,因为它使用的是AMD规范. 此规范定义的具体内容包括: Requirerequire是一

大前端技术系列:TWA技术+TensorFlow.js =&gt; 集成原生和AI功能的app

大前端技术系列:TWA技术+TensorFlow.js => 集成原生和AI功能的app ( 本文内容为melodyWxy原作,git地址:https://github.com/melodyWxy/twa-tf.js , ) 什么是TWA 简单来讲,TWA(Trusted Web Activity 可信任的网络应用)即: 基于Chrome Custom Tabs,利用谷歌浏览器提供的api,实现强大功能的桌面应用技术. 如果说你对PWA这个概念有所了解,那么TWA的实现就相当于 PWA + 更丰

Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送,写这个系列真的很要命,你要去把他们的API文档大致的翻阅一遍,而且各种功能都实现一遍,解决各种bug各种坑,不得不说,极光推送真坑,大家使用还是要慎重,我们看一下极光推送的官网 https://www.jpush.cn/common/ 推送比较使用,很多软件有需要,所以在这个点拿出来多讲讲,我们本节

为什么原生 JavaScript 开发越来越多受欢迎?是否应该跟风用原生JavaScript代替 jQuery等库?

本文标签:  jQuery的作用 原生JavaScript优势 jQuery官网 jQuery处理DOM和跨浏览器 JavaScript新特性 互联网杂谈 随着 JavaScript 本身的完善,越来越多的人开始喜欢使用原生 JavaScript 开发代替各种库,其中不少人发出了用原生 JavaScript 代替 jQuery 的声音.这并不是什么坏事,但也不见得就是好事.如果你真的想把 jQuery从前端依赖库中移除掉,我建议你慎重考虑. 首先 jQuery 是一个第三方库.库存在的价值之一在

原生JavaScript技巧大收集100个

原生JavaScript技巧大收集 1.原生JavaScript实现字符串长度截取function cutstr(str, len) { var temp; var icount = 0; var patrn = /[^\x00-\xff]/; var strre = ""; for (var i = 0; i < str.length; i++) { if (icount < len - 1) { temp = str.substr(i, 1); if (patrn.ex

原生javascript获取dom元素简单介绍

原生javascript获取dom元素简单介绍: 使用jQuery可以各种方式获取元素,比如id选择器,类选择器,元素选择器等等,非常的方便. 下面就介绍一下如何利用原生的js实现获取dom元素的功能. 一.通过id获取元素: 最方便的那就是使用document.getElementById()函数. 具体可以参阅document.getElementById()一章节. 二.通过标签获取元素: 使用document.getElementsByTagName()函数. 具体可以参阅documen

web?混合?原生?移动开发的三种模式选择

原文网址链接:http://www.ctocio.com/mobile/10169.html 今天,消费者在移动app应用(包括原生和混合)上花费的时间(94分钟/天)超过移动web(72分钟/天),于是"移动优先"成为移动开发战略的热门口号,但实际上移动web和移动app又有各自不可替代的优势,不同的移动开发模式都有成功的案例,企业需要根据自身的产品和业务属性.移动战略及目标用户需求,选择适合自己的开发道路. 下面是AppCloud制作的一张信息图,为我们详细对比了纯web(HTML

原生javascript实现获取指定元素下所有后代元素代码实例

原生javascript实现获取指定元素下所有后代元素代码实例:在本版块介绍过实现此功能的代码代码,但是在代码量上比较麻烦,采用的是循环递归的方式.上面你所说的方式可以参阅原生的javascript获取指定元素下所有的元素节点一章节,此文介绍的看起来非常的麻烦,那么下面就分享一个比较简单的方式,使用原生的javascript方法即可实现此功能.代码实例如下: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset=&