基于"发布-订阅"的原生 JS 插件封装

论坛 期权论坛 期权     
掘金开发者社区   2019-6-16 23:25   1735   0
大家好,我是神三元。
今天我们来做一个小玩意,用原生 JS 封装一个动画插件。效果如下:

这个飞驰的小球看起来是不是特有灵性呢?没错,它就是用原生JS实现的。
接下来,就让我们深入细节,体会其中的奥秘。相信这个实现的过程,会比动画本身更加精彩!
[h3]一、需求分析[/h3]封装一个插件,将小球的 DOM 对象作为参数传入,使得小球在鼠标按下和放开后能够运动,在水平方向做匀减速直线运动,初速度为鼠标移开瞬间的速度,在竖直方向的运动类似于自由落体运动。并且,小球的始终在不离开浏览器的边界运动,碰到边界会有如图的反弹效果。
[h3]二、梳理思路[/h3]分析这样的一个过程,其中大致会经历一下的关键步骤:
  • 1、鼠标按下时,记录小球的初始位置信息
  • 2、鼠标按下后滑动,记录松开鼠标瞬间的移动速度
  • 3、鼠标松开后,在水平方向上,让小球根据刚刚记录的移动速度进行匀减速运动,竖直方向设定一个竖直向下的加速度,开始运动。
  • 4、水平方向速度减为 0 时,水平方向运动停止;竖直方向速度减为 0 或者足够小时,竖直方向运动停止。
[h3]三、难点分析[/h3]看到这里,估计你的思路清晰了不少,但可能还是有一些比较难以搞定的问题。
首先,你怎么拿到松开手瞬间的小球移动速度?如何去表达出这个加速度的效果?
在实现方面,这是非常重要的问题。不过,其实非常的简单。
浏览器本身就是存在反应时间的,你可以把它当做一个摄像机,在给 DOM 元素绑定了事件之后,每隔一段时间(一般非常的短,根据不同浏览器厂商和电脑性能而定,这里我用到 chrome,保守估计为 20ms)会给这个元素拍张照,记录它的状态。在按下鼠标之后的拖动过程中,事实上会给元素拍摄无数张照片。如果现在每经过一段时间,我记录当下当前照片与上一段照片的位置差,那么最后一次拍照和倒数第二次拍照的小球位置差距,是不是就可以作为离开的瞬时速度呢?当然可以啦。废话不多说,上图:


同样,对实现加速度的效果,首先弄清一个问题,什么是速度?速度就是单位时间内运动的距离,这里暂且把它当做 20ms 内的距离,那么我每次拍照时,将这个距离增加或减少一个值,这个值就是加速度。
[h3]四、初步实现[/h3]当大部分问题考虑清楚之后,现在开始实现。
首先是基本的样式,比较简单。
    1. [/code]
    2. [*][code]
    复制代码
    1. [/code]
    2. [*][code]   
    复制代码
    1.     狂奔的小球
    复制代码
    1.    
    复制代码
    1.    
    复制代码
    1.         html, body {
    复制代码
    1.             height: 100%;
    复制代码
    1.             overflow: hidden;
    复制代码
    1.         }
    复制代码
    1.         #box{
    复制代码
    1.             position: absolute;
    复制代码
    1.             top: 100px;
    复制代码
    1.             left: 100px;
    复制代码
    1.             width: 150px;
    复制代码
    1.             height: 150px;
    复制代码
    1.             border-radius: 50%;
    复制代码
    1.             background: lightcoral;
    复制代码
    1.             cursor: move;
    复制代码
    1.             z-index: 0;
    复制代码
    1.         }
    复制代码
    1.    
    复制代码
    1. [/code]
    2. [*][code]
    复制代码
    1.    
    复制代码
    1. [/code]
    2. [*][code]
    复制代码
现在来完成核心的 JS 代码,采用 ES6 语法
    1. //drag.js
    复制代码
    1. class Drag {
    复制代码
    1.     //ele为传入的DOM对象
    复制代码
    1.     constructor(ele) {
    复制代码
    1.             //初始化参数
    复制代码
    1.         this.ele = ele;
    复制代码
    1.         ['strX', 'strY', 'strL', 'strT', 'curL', 'curT'].forEach(item => {
    复制代码
    1.             this[item] = null;
    复制代码
    1.         });
    复制代码
    1.         //为按下鼠标绑定事件,事件函数一定要绑定this,在封装过程中this统一指定为实例对象,下不赘述
    复制代码
    1.         this.DOWN = this.down.bind(this);
    复制代码
    1.         this.ele.addEventListener('mousedown', this.DOWN);
    复制代码
    1.     }
    复制代码
    1.     down(ev) {
    复制代码
    1.         let ele = this.ele;
    复制代码
    1.         this.strX = ev.clientX;//鼠标点击处到浏览器窗口最左边的距离
    复制代码
    1.         this.strY = ev.clientY;//鼠标点击处到浏览器窗口最上边的距离
    复制代码
    1.         this.strL = ele.offsetLeft;//元素到浏览器窗口最左边的距离
    复制代码
    1.         this.strT = ele.offsetTop;//元素到浏览器窗口最上边的距离
    复制代码

    1.         this.MOVE = this.move.bind(this);
    复制代码
    1.         this.UP = this.up.bind(this);
    复制代码
    1.         document.addEventListener('mousemove', this.MOVE);
    复制代码
    1.         document.addEventListener('mouseup', this.UP);
    复制代码

    1.         //flag
    复制代码
    1.         //清理上一次点击形成的一些定时器和变量
    复制代码
    1.         clearInterval(this.flyTimer);
    复制代码
    1.         this.speedFly = undefined;
    复制代码
    1.         clearInterval(this.dropTimer);
    复制代码
    1.     }
    复制代码
    1.     move(ev) {
    复制代码
    1.         let ele = this.ele;
    复制代码
    1.         this.curL = ev.clientX - this.strX + this.strL;
    复制代码
    1.         this.curT = ev.clientY - this.strY + this.strT;
    复制代码
    1.         ele.style.left = this.curL + 'px';
    复制代码
    1.         ele.style.top = this.curT + 'px';
    复制代码

    1.         //flag
    复制代码
    1.         //功能: 记录松手瞬间小球的速度
    复制代码
    1.         if (!this.lastFly) {
    复制代码
    1.             this.lastFly = ele.offsetLeft;
    复制代码
    1.             this.speedFly = 0;
    复制代码
    1.             return;
    复制代码
    1.         }
    复制代码
    1.         this.speedFly = ele.offsetLeft - this.lastFly;
    复制代码
    1.         this.lastFly = ele.offsetLeft;
    复制代码
    1.     }
    复制代码
    1.     up(ev) {
    复制代码
    1.         //给前两个事件解绑
    复制代码
    1.         document.removeEventListener('mousemove', this.MOVE);
    复制代码
    1.         document.removeEventListener('mouseup', this.UP);
    复制代码

    1.         //flag
    复制代码
    1.         //水平方向
    复制代码
    1.         this.horizen.call(this);
    复制代码
    1.         this.vertical.call(this);
    复制代码
    1.     }
    复制代码
    1.     //水平方向的运动
    复制代码
    1.     horizen() {
    复制代码
    1.         let minL = 0,
    复制代码
    1.             maxL = document.documentElement.clientWidth - this.ele.offsetWidth;
    复制代码
    1.         let speed = this.speedFly;
    复制代码
    1.         speed = Math.abs(speed);
    复制代码
    1.         this.flyTimer = setInterval(() => {
    复制代码
    1.             speed *= .98;
    复制代码
    1.             Math.abs(speed) = maxL) {
    复制代码
    1.                 this.ele.style.left = maxL + 'px';
    复制代码
    1.                 speed *= -1;
    复制代码
    1.                 return;
    复制代码
    1.             }
    复制代码
    1.             //小球到达视口最右端,反弹
    复制代码
    1.             if (curT  {
    复制代码
    1.             speed += 10;
    复制代码
    1.             speed *= .98;
    复制代码
    1.             Math.abs(speed) = maxT) {
    复制代码
    1.                 this.ele.style.top = maxT + 'px';
    复制代码
    1.                 speed *= -1;
    复制代码
    1.                 return;
    复制代码
    1.             }
    复制代码
    1.             //小球落在视口底部,反弹
    复制代码
    1.             if (curT  item === fn ? isExist = true : null);
    复制代码
    1.         !isExist ? pond.push(fn) : null;
    复制代码
    1.     }
    复制代码
    1.     remove(fn) {
    复制代码
    1.         let pond = this.pond;
    复制代码
    1.         pond.forEach((item, index) => {
    复制代码
    1.             if(item === fn) {
    复制代码
    1.                 //提一下我在这里遇到的坑,这里如果写item=null是无效的
    复制代码
    1.                 //例子:let a = {name: funtion(){}};
    复制代码
    1.                 //let b = a.name;
    复制代码
    1.                 //这个时候操作b的值对于a的name属性是没有影响的
    复制代码
    1.                 pond[index] = null;
    复制代码
    1.             }
    复制代码
    1.         })
    复制代码
    1.     }
    复制代码
    1.     fire(...arg) {
    复制代码
    1.         let pond = this.pond;
    复制代码
    1.         for(let i = 0; i  {
    复制代码
    1.     subscribe.fire(100, 200);
    复制代码
    1. }, 1000);
    复制代码
结果:

确定过眼神,你就是对的 Subscribe。(手动滑稽)
[h3]五、优化代码[/h3]
    1. //Drag.js
    复制代码
    1. if (typeof Subscribe === 'undefined') {
    复制代码
    1.     throw new ReferenceError('没有引入subscribe.js!');
    复制代码
    1. }
    复制代码

    1. class Drag {
    复制代码
    1.     constructor(ele) {
    复制代码
    1.         this.ele = ele;
    复制代码
    1.         ['strX', 'strY', 'strL', 'strT', 'curL', 'curT'].forEach(item => {
    复制代码
    1.             this[item] = null;
    复制代码
    1.         });
    复制代码

    1.         this.subDown = new Subscribe;
    复制代码
    1.         this.subMove = new Subscribe;
    复制代码
    1.         this.subUp = new Subscribe;
    复制代码

    1.         //=>DRAG-START
    复制代码
    1.         this.DOWN = this.down.bind(this);
    复制代码
    1.         this.ele.addEventListener('mousedown', this.DOWN);
    复制代码
    1.     }
    复制代码

    1.     down(ev) {
    复制代码
    1.         let ele = this.ele;
    复制代码
    1.         this.strX = ev.clientX;
    复制代码
    1.         this.strY = ev.clientY;
    复制代码
    1.         this.strL = ele.offsetLeft;
    复制代码
    1.         this.strT = ele.offsetTop;
    复制代码

    1.         this.MOVE = this.move.bind(this);
    复制代码
    1.         this.UP = this.up.bind(this);
    复制代码
    1.         document.addEventListener('mousemove', this.MOVE);
    复制代码
    1.         document.addEventListener('mouseup', this.UP);
    复制代码

    1.         this.subDown.fire(ele, ev);
    复制代码
    1.     }
    复制代码

    1.     move(ev) {
    复制代码
    1.         let ele = this.ele;
    复制代码
    1.         this.curL = ev.clientX - this.strX + this.strL;
    复制代码
    1.         this.curT = ev.clientY - this.strY + this.strT;
    复制代码
    1.         ele.style.left = this.curL + 'px';
    复制代码
    1.         ele.style.top = this.curT + 'px';
    复制代码

    1.         this.subMove.fire(ele, ev);
    复制代码
    1.     }
    复制代码

    1.     up(ev) {
    复制代码
    1.         document.removeEventListener('mousemove', this.MOVE);
    复制代码
    1.         document.removeEventListener('mouseup', this.UP);
    复制代码

    1.         this.subUp.fire(this.ele, ev);
    复制代码
    1.     }
    复制代码
    1. }
    复制代码

    1. window.Drag = Drag;
    复制代码
    1. //dragExtend.js
    复制代码
    1. function extendDrag(drag) {
    复制代码
    1.     //鼠标按下
    复制代码
    1.     let stopAnimate = function stopAnimate(curEle) {
    复制代码
    1.         clearInterval(curEle.flyTimer);
    复制代码
    1.         curEle.speedFly = undefined;
    复制代码
    1.         clearInterval(curEle.dropTimer);
    复制代码
    1.     };
    复制代码
    1.     //鼠标移动
    复制代码
    1.     let computedFly = function computedFly(curEle) {
    复制代码
    1.         if (!curEle.lastFly) {
    复制代码
    1.             curEle.lastFly = curEle.offsetLeft;
    复制代码
    1.             curEle.speedFly = 0;
    复制代码
    1.             return;
    复制代码
    1.         }
    复制代码
    1.         curEle.speedFly = curEle.offsetLeft - curEle.lastFly;
    复制代码
    1.         curEle.lastFly = curEle.offsetLeft;
    复制代码
    1.     };
    复制代码
    1.     //水平方向的运动
    复制代码
    1.     let animateFly = function animateFly(curEle) {
    复制代码
    1.         let minL = 0,
    复制代码
    1.             maxL = document.documentElement.clientWidth - curEle.offsetWidth,
    复制代码
    1.             speed = curEle.speedFly;
    复制代码
    1.         curEle.flyTimer = setInterval(() => {
    复制代码
    1.             speed *= .98;
    复制代码
    1.             Math.abs(speed) = maxL) {
    复制代码
    1.                 curEle.style.left = maxL + 'px';
    复制代码
    1.                 speed *= -1;
    复制代码
    1.                 return;
    复制代码
    1.             }
    复制代码
    1.             if (curT  {
    复制代码
    1.             speed += 10;
    复制代码
    1.             speed *= .98;
    复制代码
    1.             Math.abs(speed) = maxT) {
    复制代码
    1.                 curEle.style.top = maxT + 'px';
    复制代码
    1.                 speed *= -1;
    复制代码
    1.                 return;
    复制代码
    1.             }
    复制代码
  • [code]            if (curT
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP