【前端进阶】高级运动之弹性运动

170 阅读3分钟

弹性运动

要理解弹性运动,可以想象一个被拉伸的弹性绳子绑住的小球,小球向绑绳子的木桩运动,并围绕木桩左右运动,最终由于空气阻力的影响,停在木桩处

怎么运动呢? 向左拉小球,松手时,,当小球在木桩的左边时小球做加速运动,当小球在木桩的右边时,小球做减速运动,当小球被被右边的绳子牵引时,又会向左边做加速运动,当小球在木桩的左边时又会做减速运动。而由于空气摩擦的原因,小球最终户会停止运动。

所以在js中弹性运动其实就是在目标点左边,加速;在目标点右边,减速 也就是做加减速运动

首先我们用JS先模拟一下加速运动

<!DOCTYPE HTML>
<html>

<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>加速运动</title>
	<style>
		#div1 {
			width: 100px;
			height: 100px;
			background: red;
			position: absolute;
			left: 0;
		}
	</style>
	<script>
		window.onload = function () {
			var oInput = document.getElementById('input1');
			var oDiv = document.getElementById('div1');
			var timer = null;
			var iSpeed = 0;
			oInput.onclick = function () {
				startMove();
			};
			function startMove() {
				clearInterval(timer);
				timer = setInterval(function () {
					iSpeed += 3;
					oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';

				}, 30);
			}

		};
	</script>
</head>

<body>
	<input type="button" value="开始运动" id="input1">
	<div id="div1"></div>
</body>

</html>

减速运动

<!DOCTYPE HTML>
<html>

<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>减速运动</title>
	<style>
		#div1 {
			width: 100px;
			height: 100px;
			background: red;
			position: absolute;
			left: 0;
		}
	</style>
	<script>
		window.onload = function () {
			var oInput = document.getElementById('input1');
			var oDiv = document.getElementById('div1');

			var timer = null;
			var iSpeed = 80;

			oInput.onclick = function () {
				startMove();
			};

			function startMove() {
				clearInterval(timer);
				timer = setInterval(function () {

					iSpeed -= 3;

					oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
					console.log(oDiv.offsetLeft + iSpeed, 'oDiv.offsetLeft + iSpeed')

				}, 30);
			}

		};
	</script>
</head>

<body>
	<input type="button" value="开始运动" id="input1">
	<div id="div1"></div>
</body>

</html>

加减度运动

【无摩擦】左右一直做加减速运动

<!DOCTYPE HTML>
<html>

<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>加减度运动</title>
	<style>
		#div1 {
			width: 100px;
			height: 100px;
			background: red;
			position: absolute;
			left: 0;
		}

		#bg {
			width: 1px;
			height: 500px;
			background: black;
			position: absolute;
			left: 500px;
			top: 0;
		}
	</style>
	<script>
		window.onload = function () {
			var oInput = document.getElementById('input1');
			var oDiv = document.getElementById('div1');

			var timer = null;
			var iSpeed = 0;

			oInput.onclick = function () {
				startMove();
			};

			function startMove() {
				clearInterval(timer);
				timer = setInterval(function () {
					// 根据距离的远近来决定加速度的大小
					/*if( oDiv.offsetLeft < 500 ){
                                         //加速
						iSpeed += (500 - oDiv.offsetLeft)/50;
					}
					else{
                                          //减速
						iSpeed -= (oDiv.offsetLeft - 500)/50;
					}*/
                                         //转换之后 +-只决定运动的方向
					iSpeed += (500 - oDiv.offsetLeft) / 50;

					oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';

				}, 30);
			}

		};
	</script>
</head>

<body>
	<input type="button" value="开始运动" id="input1">
	<div id="div1"></div>
	<div id="bg"></div>
</body>

</html>

好了 重点来了。

我们高中都学过摩擦力公式 F=fM

f为摩擦系数 M为质量

但是js当中并没有力,重力的概念 。你不能说这个方块重多少吨 ,多少牛顿吧。

没有的话怎么办呢? 我们在做物体运动的时候只有速度 , 物体快就是速度快 ,物体慢呢 ,就是速度小。 所以物体的快慢都是由速度来决定的。 这样的话, 我们想一下 一个物体它如果想要停下来, 受到阻力,说白了就是让速度损失呗,速度越来越小,不就是相当于受到阻力了吗!!!所以就可以把受到阻力这个环节给跳过 ,知道到这么一回事就行,只要是速度减小,必然是受到了阻力,那怎样才能让速度损失呢?那肯定不能让速度减减(speed--) ,如果速度减成负数就往回走了。因为正负是决定物体的方向的,而不是决定物体快慢的。 所以说减减(speed--)是不靠谱的。 那如果想让速度慢下来,应该让速度越来越接近于0才行,物体才会慢慢停下来。所以如果想让速度损失,就需要让速度乘以一个小于1数 即摩擦系数

由此可以得出弹性运动公式为

速度 += (目标点 - 当前值)/系数 (+= 速度是一直在往上增大或一直减小,左右摆动) 速度 *= 摩擦系数

系数可以是6 , 7 , 8 控制物体左右摇摆的频率,系数越小,频率越快,系数越大,频率越慢 摩擦系数范围( 0.5<x<1 ) 摩擦系数越小 ,速度损失的越大,如果太小的话(小于0.5)就相当于摩擦力越大,物体第一次到目标点时受到的阻力太大,可能就停止了。摩擦系数越大的话,相当于速度损失的越小,相当于摩擦力越小。那么弹簧的伸缩性就会越大。 可别和缓冲运动公式混淆了

缓冲公式:速度 = (目标点 - 当前值)/系数 (= 是速度一直在减小 直到为0,才停止)

还没完呢

套用上面公式 虽然速度趋近于0了(也可能为负值) 但是定时器还一直开着呢,所以我们还需要在物体停止运动时清除定时器,但是这个停止运动的条件怎么判断呢?

我们先来回忆一下缓冲运动的停止条件是什么

当物体慢慢接近目标点,跟目标点重合的时候停止运动,但是弹性运动能不能也是根据物体与目标点的距离来决定停不停止呢,答案是不行。当一个物体从左边向右走走走的时候,走到目标点你就让他停止了,他还能过去吗?还能做左右摇摆运动吗?肯定不行!!!再想,从上面代码可以看出速度越来越小 越来越接近于0,所以慢慢停下来了,那我们能不能根据速度小于0或接近于0就认为它停止了呢?也不行!!!因为弹性是做左右摇摆运动,走到最右边的的时候,速度也是越来越接近于0,那你不能让物体运动到最右边就停止吧。所以只距离接近于目标点不行,只判断速度接近于0也不行。那就可以把这两个条件结合起来,当距离越来越近和速度越来越趋近于0时就是物体停止运动的条件

因为速度的正负只是决定方向的 所以需要取绝对值
if (Math.abs(speed) <= 1 && Math.abs(目标点 - 当前值) <= 1) {
	clearInterval(timer)
}

但是速度和目标点在符合判断条件时会有误差需要在停止时矫正目标点和速度 即

if (Math.abs(speed) <= 1 && Math.abs(目标点 - 当前值) <= 1) {
	clearInterval(timer)
      //矫正
      oDiv.style.left =目标点
      speed = 0;
}

根据弹性公式写的一个demo

<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>无标题文档</title>
	<style>
		#div1 {
			width: 100px;
			height: 100px;
			background: red;
			position: absolute;
			left: 0;
		}

		#bg {
			width: 1px;
			height: 500px;
			background: black;
			position: absolute;
			left: 500px;
			top: 0;
		}
	</style>
	<script>

		//摩擦力 : F = fM

		window.onload = function () {
			var oInput = document.getElementById('input1');
			var oDiv = document.getElementById('div1');

			var timer = null;
			var iSpeed = 0;

			oInput.onclick = function () {
				startMove();
			};

			function startMove() {
				clearInterval(timer);
				timer = setInterval(function () {
					/*if( oDiv.offsetLeft < 500 ){
						// 加速(速度越来越小)
						iSpeed += (500 - oDiv.offsetLeft)/50;
					}
					else{
						// 减速(速度越来越小)
						iSpeed -= (oDiv.offsetLeft - 500)/50;
						//转换
						iSpeed += -(oDiv.offsetLeft - 500)/50;//
					}*/
					//合并
					iSpeed += (500 - oDiv.offsetLeft) / 6;
					iSpeed *= 0.75;
					if (Math.abs(iSpeed) <= 1 && Math.abs(500 - oDiv.offsetLeft) <= 1) {
						clearInterval(timer);
						oDiv.style.left = '500px';
						iSpeed = 0;
					}
					else {
						oDiv.style.left = oDiv.offsetLeft + iSpeed + 'px';
					}

					document.title = oDiv.style.left + ',' + iSpeed;

				}, 30);
			}

		};
	</script>
</head>

<body>
	<input type="button" value="开始运动" id="input1">
	<div id="div1"></div>
	<div id="bg"></div>
</body>

</html>

在弹性运动当中呢 还要注意一个小细节,在IE8-浏览器存在弹性过界问题,当宽度width或高度height等不能出现负值的样式出现负值时将会报错,停止运动。但是像left,top,这样的值改变,并不会出现此问题。所以,需要判断样式为高度或宽度时,样式值小于0时,等于0。

<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>无标题文档</title>
	<style>
		#div1 {
			width: 100px;
			height: 80px;
			background: red;
		}
	</style>
	<script>
		window.onload = function () {
			var oDiv = document.getElementById('div1');

			var timer = null;
			var iSpeed = 0;

			oDiv.onmouseover = function () {
				startMove(300);
			};

			oDiv.onmouseout = function () {
				startMove(80);
			};

			function startMove(iTarget) {
				clearInterval(timer);
				timer = setInterval(function () {

					iSpeed += (iTarget - oDiv.offsetHeight) / 6;
					iSpeed *= 0.75;

					if (Math.abs(iSpeed) <= 1 && Math.abs(iTarget - oDiv.offsetHeight) <= 1) {
						clearInterval(timer);
						iSpeed = 0;
						oDiv.style.height = iTarget + 'px';
					}
					else {

						var H = oDiv.offsetHeight + iSpeed;

						if (H < 0) {
							H = 0;
						}

						oDiv.style.height = H + 'px';
					}

				}, 30);
			}

		};
	</script>
</head>

<body>
	<div id="div1"></div>
</body>

</html>

框架封装

封装成框架进行调用,可以解决多物体运动或者多个值同时运动的问题

/**
 * @param {Object}   当前运动物体的实例
 * @param {Object}   运动物体的属性值
 * @param {Function} 运动完成时的回调
 * @example  startMove(this,{top:20},function(){})
 */
function startMove(obj, json, fn) {
    clearInterval(obj.timer);

    var iSpeed = {};
    for (var attr in json) {
        iSpeed[attr] = 0;
    }

    obj.timer = setInterval(function () {

        var bBtn = true;

        for (var attr in json) {

            var iCur = 0;
            iCur = parseInt(getStyle(obj, attr));

            iSpeed[attr] += (json[attr] - iCur) / 6;
            iSpeed[attr] *= 0.75;

            if (Math.abs(iSpeed[attr]) > 1 || Math.abs(json[attr] - iCur) > 1) {

                bBtn = false;
            }

            var value = iCur + iSpeed[attr];

            if (value < 0 && (attr == 'width' || attr == 'height')) {
                value = 0;
            }
            obj.style[attr] = value + 'px';


        }

        if (bBtn) {
            clearInterval(obj.timer);
            for (var attr in json) {
                iSpeed[attr] = 0;
                obj.style[attr] = json[attr] + 'px';
            }
            fn && fn.call(obj);
        }

    }, 30);
}


function getStyle(obj, attr) {
    if (obj.currentStyle) {
        return obj.currentStyle[attr];
    }
    else {
        return getComputedStyle(obj, false)[attr];
    }
}
//调用
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function () {
    startMove(this, {
        height: 500
    }, () => {
        startMove(this, {
            width: 400
        })
    })
};