JavaScript 运动 04 —— 弹性运动

弹性运动

弹性运动是物体在某个参考点两侧进行运动。

弹性运动.gif

加速运动和减速运动

加速运动是速度逐渐增加的运动,减速运动是速度逐渐减小的运动。根据物理规律,我们知道:

  • 当物体靠近参考点时,做加速运动
  • 当物体原理参考点时,做减速运动

简单的弹性运动

根据上面的规律,我们来实现一个简单的弹性运动,首先修改一下页面布局,使之更适应于弹性运动:

...
#par::after{
    content: "";
    display: block;
    width: 1px;
    height: 310px;
    position: absolute;
    left: 50%;
    top: 0;
    background: red;
    z-index: -1;
}
...

修改后的布局是这个样子的:

弹性运动场景.png

小滑块将在红线的两侧进行弹性运动。
修改 animate 函数,实现一个最简单的弹性运动:

function animate(ele = null,config = {
    accel:10,
    attrs:{},
}){
    // 清除定时器
    clearInterval(ele.timer);
    const attrs = config.attrs;
    const accel = config.accel||10;
    let speed = 0;
    return new Promise((resolve)=>{
        ele.timer = setInterval(()=>{
            for(const attr in attrs){
                // 设置目标距离
                let target = Number.parseInt(attrs[attr]);

                // 获取当前的样式
                let currentStyle = (attr === "opacity")?(Number.parseInt(Number.parseFloat(getCurrentStyle(ele,attr))*100)):Number.parseInt(getCurrentStyle(ele,attr));

                // 如果改变的样式是 opacity,target乘以100
                if(attr === "opacity"){
                    target = Number.parseInt(Number.parseFloat(attrs[attr])*100);
                }

                // 获取速度
                if(currentStyle < target){
                    speed += accel;
                }else{
                    speed -= accel;
                }

                // 根据当前样式动态改变物体的样式
                ele.style[attr] = (attr === "opacity")?( currentStyle + speed)/100:(currentStyle + speed) + "px";
            }

        },30);
    });
}

调用运动函数:

const ele = document.getElementById("inner");
async function start(){
    await animate(ele,{
        accel:10,
        attrs:{
            left:"250px",
        }
    });
}

看下效果:

这里的 accel 表示加速度,当小滑块靠近红线时,速度越来越大,当小滑块远离红线时,速度越来越小。

更真实的加速度

上面的加速度采用手动设置的方式,虽然可以设定不同的值,但仍然不太真实。具体说来,加速度应该是不断变化的,在距离参考点最远的地方,加速度最大,在距离参考点最近的地方,加速度最小。于是我们将加速度调整为函数内部计算:

function animate(ele = null,config = {
    scale:20,
    coeff:0.95,
    attrs:{
    },
}){
    // 清除定时器
    clearInterval(ele.timer);
    const attrs = config.attrs;
    const scale = config.scale||20;
    const coeff = config.coeff||0.95;
    let speed = 0;
    return new Promise((resolve)=>{
        ele.timer = setInterval(()=>{
            for(const attr in attrs){
                // 设置目标距离
                let target = Number.parseInt(attrs[attr]);

                // 获取当前的样式
                let currentStyle = (attr === "opacity")?(Number.parseInt(Number.parseFloat(getCurrentStyle(ele,attr))*100)):Number.parseInt(getCurrentStyle(ele,attr));

                // 如果改变的样式是 opacity,target乘以100
                if(attr === "opacity"){
                    target = Number.parseInt(Number.parseFloat(attrs[attr])*100);
                }

                // 获取速度
                speed += (target - currentStyle)/scale;
                // 根据当前样式动态改变物体的样式
                ele.style[attr] = (attr === "opacity")?( currentStyle + speed)/100:(currentStyle + speed) + "px";
            }

        },30);
    });
}

scale 是缩放系数,由使用者设置。

加入摩擦力

上面的弹性运动中,速度是没有损失的,于是小滑块就一直在红线两侧往复运动。有时候我们想让弹性运动逐渐停下来,于是就需要加入一个摩擦系数。
修改 animate 函数:

function animate(ele = null,config = {
    scale:5,
    coeff:0.7,
    attrs:{
    },
}){
    // 清除定时器
    clearInterval(ele.timer);
    const attrs = config.attrs;
    const scale = config.scale||5;
    const coeff = config.coeff||0.7;
    let speed = 0;
    return new Promise((resolve)=>{
        ele.timer = setInterval(()=>{
            for(const attr in attrs){
                // 设置目标距离
                let target = Number.parseInt(attrs[attr]);

                // 获取当前的样式
                let currentStyle = (attr === "opacity")?(Number.parseInt(Number.parseFloat(getCurrentStyle(ele,attr))*100)):Number.parseInt(getCurrentStyle(ele,attr));

                // 如果改变的样式是 opacity,target乘以100
                if(attr === "opacity"){
                    target = Number.parseInt(Number.parseFloat(attrs[attr])*100);
                }

                // 获取速度
                speed += (target - currentStyle)/scale;
                speed *= coeff;

                // 根据当前样式动态改变物体的样式
                ele.style[attr] = (attr === "opacity")?( currentStyle + speed)/100:(currentStyle + speed) + "px";
            }

        },30);
    });
}

调用动画函数:

const ele = document.getElementById("inner");
async function start(){
    await animate(ele,{
        attrs:{
            left:"300px",
        }
    });
}

查看效果:

加入摩擦系数.gif

弹性运动停止条件

和其他运动一样,弹性运动也需要在某一时刻停止,弹性运动的停止条件为:速度足够小并且目标点和当前位置差值的绝对值足够小。现在我们来修改 animate 函数,使弹性运动可以停止,同时增加摩擦因子判断:摩擦因子不能大于 1 。
修改 animate 函数:

function animate(ele = null,config = {
    scale:5,
    coeff:0.7,
    attrs:{
    },
}){
    // 清除定时器
    clearInterval(ele.timer);
    const attrs = config.attrs;
    const scale = config.scale||5;
    const coeff = config.coeff||0.7;

    if(coeff > 1){
        throw new Error("错误的摩擦因数值");
    }

    let speed = 0;
    return new Promise((resolve)=>{
        ele.timer = setInterval(()=>{
            for(const attr in attrs){
                // 设置目标距离
                let target = Number.parseInt(attrs[attr]);

                // 获取当前的样式
                let currentStyle = (attr === "opacity")?(Number.parseInt(Number.parseFloat(getCurrentStyle(ele,attr))*100)):Number.parseInt(getCurrentStyle(ele,attr));

                // 如果改变的样式是 opacity,target乘以100
                if(attr === "opacity"){
                    target = Number.parseInt(Number.parseFloat(attrs[attr])*100);
                }

                // 获取速度
                speed += (target - currentStyle)/scale;
                speed *= coeff;

                if(Math.abs(speed)<1 && Math.abs(target - currentStyle)<1){
                    clearInterval(ele.timer);
                    // 清除定时器后,将元素移动到目标点
                    ele.style[attr] = (attr === "opacity")?target / 100: target + "px";
                    resolve();
                }else{
                    ele.style[attr] = (attr === "opacity")?( currentStyle + speed)/100:(currentStyle + speed) + "px";
                }

            }

        },30);
    });
}

调用运动函数:

const ele = document.getElementById("inner");
async function start(){
    await animate(ele,{
        coeff:0.9,
        attrs:{
            left:"300px",
        }
    });
}

最终效果:


弹性运动最终效果.gif

调整 config 中的 scale 和 coeff 属性值,可以设置不同的动态效果。

多值弹性运动

基于现有的运动框架,该运动同样支持多值运动和链式调用。下面是一个多值运动的 demo:

const ele = document.getElementById("inner");
async function start(){
    await animate(ele,{
        coeff:0.9,
        attrs:{
            width:"200px",
            height:"200px",
            lineHeight:"200px",
        }
    });
}

效果图如下:

弹性运动多值运动.gif

总结

至此,弹性运动就算说完了,总结一下有以下几点需要注意的地方:

  • 物体靠近参考点,做加速运动
  • 物体远离参考点,做减速运动
  • 加速度最好是随着距离做动态变化的
  • 实际情况中的弹性运动是需要停止的,因此需要摩擦系数
  • 弹性运动的停止条件为:速度足够小并且当前位置和目标位置差值的绝对值足够小

在下一篇文章,我们将会实现常用运动中的最后一种运动形式:碰撞运动。

完。

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

推荐阅读更多精彩内容

  • 好多小伙伴们都在希望自己高中物理能考得棒棒哒,可是怎样验证自己学习到位了呢?物理君准备高考物理考点120问,看看你...
    物理君阅读 2,292评论 0 41
  • 1 今天早晨,在出门上班前完成了昨天检视、今日计划早读英语、1分钟分享,这是因为我开始计算晨间练习312经络锻炼...
    LiHongxi阅读 127评论 0 0
  • 文|图囿 你是沙漠里的海市蜃楼, 亦真亦假; 你是人世间的一场烟雨, 如梦如痴。 你若不来, 我便沉睡不醒。 春天...
    图囿阅读 335评论 2 2