1. 运动所运用的属性
- 移动:
offsetTop,offsetLeft,style.top,style.left - 透明度:
style.opacity - 宽高:
offsetWidth,offsetHeight,style.width,style.height
1.1 offset和style的优缺点
offset系列:
- 没有
opacity offsetWidth的值为元素总宽,横向递增会附加左右边距,横向递减又会抵消左右边距(offsetHeight同理)
style系列:
- 属性齐全,但只能获得行内样式
<style>
#box {
width: 200px;
height: 200px;
border: 1px solid #fff;
background-color: #d60231;
/* box-sizing: border-box; */
}
</style>
<div></div>
<script>
var oDiv = document.getElementsByTagName('div')[0];
setInterval(function() {
oDiv.style.width = oDiv.offsetWidth - 2 + 'px'; // !元素保持不动,可以设置box-sizing解决
}, 20);
</script>
1.2 使用计算属性获取样式
要点: window.getComputedStyle(el, null)[attr]
function getStyle(el, attr) {
return window.getComputedStyle(el, null)[attr];
}
2. 匀速运动
2.1 位置匀速运动
匀速移动:元素最终位置(style.left)= 当前位置(el.offsetLeft) + 递增值(pace)
判断方位:起始点 < 目标点 ? +pace : -pace
function move(el, target) {
var current = el.offsetLeft; // 1. 获取元素当前位置
pace = current < target ? 2 : -2; // 2. 判断递增or递减
clearInterval(el.timer); // 3. 清除定时器,防止累加
el.timer = setInterval(function () { // 4. 设置定时器
current = el.offsetLeft; // 4.1 获取当前位置
if (current === target) { // 4.2 查看是否达到目标点并清除定时器
clearInterval(el.timer);
return false; // 4.3 达到目标点清除定时器并退出函数(不然会多加一次)
}
el.style.left = current + pace + 'px'; // 4.3 设置定位置元素移动
}, 20);
}
2.2 透明度匀速运动
- 由于
JS处理浮点数的精度问题,需要以(100 - 0)的区间处理透明度 - 虽然
String * 100会自动转为Number,但使用parseFloat()是个好习惯 - 可能出现
0.07 * 100 = 7.000000000000001这种情况,需要用Math.round()处理下
function move(el, target) {
var current = Math.round( getStyle(el, 'opacity') * 100 ); // 1. 获取元素透明度
pace = current < target ? 2 : -2; // 2. 判断递增or递减
clearInterval(el.timer); // 3. 清除定时器,防止累加
el.timer = setInterval(function () { // 4. 设置定时器
current = Math.round( getStyle(el, 'opacity') * 100 ); // 4.1 获取透明度
if (current === target) { // 4.2 查看是否达到目标点并清除定时器
clearInterval(el.timer);
return false; // 4.3 达到目标点清除定时器并退出函数(不然会多加一次)
}
el.style.opacity = (current + pace) / 100; // 4.3 缩小100倍,取消'px'
}, 20);
}
3. 缓冲运动(常用)
缓冲要点: pace会随着距离的缩短而逐渐衰减,呈现先快后慢的趋势
- 当前值(
current)放入定时器实时计算 - 递增值
pace = (target - current) / 10 - 如果
pace的值小于1的话,获取的元素值会向下取整,就等于没有递增,陷入死循环,所以pace需要取整 pace = pace > 0 ? Math.ceil(pace) : Math.floor(pace);
function move(el, target) {
var current = 0,
pace = 0;
clearInterval(el.timer);
el.timer = setInterval(function() {
current = parseInt( getStyle(el, 'left') ); // 1. 获取当前值,去'px'
pace = (target - current) / 10; // 2. 计算出步长
pace = pace > 0 ? Math.ceil(pace) : Math.floor(pace); // 3. 取整步长
if(current === target) { // 4. 判断是否达到目标点,终止定时器
clearInterval(el.timer);
return false;
}
el.style.left = current + pace + 'px'; // 3. 元素实际移动
}, 20); // 20是因为 1000 / 20 = 50 帧, 比较流畅
}
3.1 多元素运动
多个元素同时运动,互不影响
多物体要点:定时器捆绑到元素自身(之前的例子也都这么做的)
clearInterval(el.timer);
el.timer = setInterval(function () {}, 20);
3.2 多属性封装
属性分为两类:
- 有单位:
top,left,width,height - 无单位:
opacity
多属性要点:
- 使用
Object传参,for in循环属性值 - 有单位、无单位分别处理
move(el, { 'left': 200 }, 20);
function move(el, paras, speed) {
var target = 0,
current = 0,
pace = 0;
clearInterval(el.timer);
el.timer = setInterval(function() {
for(var attr in paras) {
target = paras[attr]; // 1. 获取目标值
// 2. 判断并获取当前值
if(attr === 'opacity') {
current = Math.round( parseFloat( getStyle(el, attr) ) * 100 );
} else {
current = parseInt( getStyle(el, attr) );
}
// 3. 判断当前值是否达到目标值
if(current === target) {
clearInterval(el.timer);
return false;
}
// 4. 计算出步长
pace = (target - current) / (speed || 10);
pace = pace > 0 ? Math.ceil(pace) : Math.floor(pace);
// 5. 应用
if(attr === 'opacity') {
el.style[attr] = (current + pace) / 100;
} else {
el.style[attr] = current + pace + 'px';
}
}
}, 20);
}
3.3 多属性互不干扰
弥补缺点: 如果多个属性同时运动的话,会出现最先完成的属性终止定时器的情况,影响到其他属性
要点: 使用isReach,各个属性运动前重置该属性(false),如果都达成目标则为(true),清除定时器
function move(el, paras, callback, speed) { // 1. 添加 callback 形参
var target = 0,
current = 0,
pace = 0,
isReach = false;
clearInterval(el.timer);
el.timer = setInterval(function() {
isReach = true;
for(var attr in paras) {
// ......
if(current !== target) {
isReach = false;
}
// ......
}
if(isReach) {
clearInterval(el.timer);
return false;
}
}, 20);
}
3.4 链式运动
链式运动: 移动 --> 透明度 --> dispaly: none;
链式运动要点: 回调函数(异步改同步)
- 增加了
callback参数 - 使用
call改变this指向方便使用
var oBox = document.getElementById('box');
move(oBox, { 'left': 200 }, function() {
move(this, { 'opacity': 0 }, function() {
this.style.display = 'none';
}
});
function move(el, paras, speed, callback) { // 1. 添加 callback 形参
// var target......
clearInterval(el.timer);
el.timer = setInterval(function() {
isReach = true;
for(var attr in paras) {
// ......
}
if(isReach) {
clearInterval(el.timer);
callback && callback.call(el); // 2. 如果callback存在则运行
return false;
}
}, 20);
}
8. IE兼容性
8.1 计算属性
function getStyle(el, attr) {
if(el.currentStyle) {
return el.currentStyle[attr];
} else {
return window.getComputedStyle(el, null)[attr];
}
}
8.2 透明度
el.style.filter = 'alpha(opacity=100)'; // IE
End
function move(el, paras, callback, speed) {
var target = 0,
current = 0,
pace = 0,
isReach = false;
clearInterval(el.timer);
el.timer = setInterval(function () {
isReach = true
for (var attr in paras) {
target = paras[attr];
if (attr === 'opacity') {
current = Math.round(parseFloat(getStyle(el, attr)) * 100);
} else {
current = parseInt(getStyle(el, attr));
}
if (current !== target) {
isReach = false;
}
pace = (target - current) / (speed || 10);
pace = pace > 0 ? Math.ceil(pace) : Math.floor(pace);
if (attr === 'opacity') {
el.style[attr] = (current + pace) / 100;
} else {
el.style[attr] = current + pace + 'px';
}
}
if (isReach) {
clearInterval(el.timer);
callback && callback.call(el);
return false;
}
}, 20);
}