一、运动函数的基本实现
- 运动函数是我们自己封装的一个函数。
- 作用是将css样式的改变不是一次性完成,是逐步完成执行效果,看上去像是动画/运动完成的css样式改变。
- 实际项目中框架等都有自己的运动函数我们目前封装一个简单的兼容多属性的运动函数。
1、HTML和CSS代码
<div class="box1"></div>
<div class="box2"></div>
<style>
* {
padding: 0;
margin: 0;
}
.box1 {
width: 100px;
height: 100px;
background-color: pink;
position: absolute;
top: 0;
left: 100;
}
.box2 {
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top: 200px;
left: 0;
}
</style>
3、代码的实现
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
oDiv1.onclick = function () {
// 定义变量 用与步长的累加
let count = 0;
// 定时器的this指向是window 改变this指向
const that = this;
// 定时器
const timer = setInterval(function () {
count += 5;
that.style.left = count + 'px';
// 清除定时器
if (count == 100) {
clearInterval(timer);
}
}, 50)
}
oDiv2.onclick = function () {
// 定义变量 用与步长的累加
let count = 0;
// 定时器的this指向是window 改变this指向
const that = this;
// 定时器
const timer = setInterval(function () {
count += 5;
that.style.left = count + 'px';
// 清除定时器
if (count == 100) {
clearInterval(timer);
}
}, 50)
}
二、封装函数
// 封装函数
function move(element, type, target) {
/**
* @param element 移动的标签对象
* @param type 标签属性属性
* @param target 移动的目标距离
*/
// 定义变量 用与步长的累加
let count = 0;
// 定时器
const timer = setInterval(function () {
count += 5;
element.style[type] = count + 'px';
// 清除定时器
if (count == target) {
clearInterval(timer);
}
}, 50)
}
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, 'left', 200)
})
oDiv2.addEventListener('click', function () {
move(oDiv2, 'left', 400)
})
三、代码优化
(一)优化一
- 问题: 每次运动都从0的位置开始移动
- 原因: 运动函数的 初始值, 是从0开始的
- 解决: 获取到元素本身值, 然后再次基础上移动
// 封装函数
function move(element, type, target) {
/**
* @param element 移动的标签对象
* @param type 标签属性属性
* @param target 移动的目标距离
*/
// 定义变量 用与步长的累加
let count = parseInt(window.getComputedStyle(element)[type]);
console.log(count );
// 定时器
const timer = setInterval(function () {
count += 5;
element.style[type] = count + 'px';
// 清除定时器
if (count == target) {
clearInterval(timer);
}
}, 50)
}
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, 'left', 200)
})
oDiv2.addEventListener('click', function () {
move(oDiv2, 'top', 400)
})
(二)优化二
1、问题: 从200移动到501时, 不会停止
2、原因: 移动距离, 不是每次移动的整倍数;移动距离301(501-200),是每次运动的整倍数
3、解决
4、代码实现
// 封装函数
function move(element, type, target) {
/**
* @param element 移动的标签对象
* @param type 标签属性属性
* @param target 移动的目标距离
*/
// 定时器
const timer = setInterval(function () {
// 获取当前的位置
let count = parseInt(window.getComputedStyle(element)[type]);
// 计算要移动的距离 (目标 - 当前的值) / 10
let des = (target - count) / 10;
// 判断是否需要继续移动
if (target === count) {
// 此时说明移动到了, 可以结束定时器
clearInterval(timer);
} else {
// 此时说明还没有移动到, 需要继续移动
element.style[type] = count + des + 'px';
}
}, 20)
}
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, 'left', 501)
})
oDiv2.addEventListener('click', function () {
move(oDiv2, 'left', 100)
})
(三)优化三
// 封装函数
function move(element, type, target) {
/**
* @param element 移动的标签对象
* @param type 标签属性属性
* @param target 移动的目标距离
*/
// 定时器
const timer = setInterval(function () {
// 获取当前的位置
let count = parseInt(window.getComputedStyle(element)[type]);
// 计算要移动的距离 (目标 - 当前的值) / 10
let des = (target - count) / 10;
// 大于0向上取整 小于0向下取整
des = des > 0 ? Math.ceil(des) : Math.floor(des);
// 判断是否需要继续移动
if (target === count) {
// 此时说明移动到了, 可以结束定时器
clearInterval(timer);
} else {
// 此时说明还没有移动到, 需要继续移动
element.style[type] = count + des + 'px';
}
}, 20)
}
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, 'left', 0)
})
oDiv2.addEventListener('click', function () {
move(oDiv2, 'left', 200)
})
四、兼容透明度
/**
* 问题: 透明没有调整
*
* 原因: 透明度没有单位
*
* 解决: 在赋值的时候, 判断如果时透明度, 那么就别带单位
*
* 新问题: 没有运动过程
*
* 原因: 因为做了取整之后, 得到的值, 只会是 -1或者1
*
* 解决: 想个办法, 将值放大
*/
// 封装函数
function move(element, type, target) {
// 透明度 0.1 --> 1
if(type === 'opacity') target *= 100;
// target === 100
let timer = setInterval(function(){
// 获取当前位置的值 兼容透明度
let count = type == 'opacity' ? window.getComputedStyle(element)[type]*100 : parseInt(window.getComputedStyle(element)[type]);
// count === 0.1 * 100 === 10
// 计算要移动的距离
let des = (target - count) / 10;
// destination == (target - count) / 10 === (100 - 10) / 10 === 9
des = des > 0 ? Math.ceil(des) : Math.floor(des);
// destination === 9
// 清除定时器
if(des === count){
clearInterval(timer);
}else{
if(type === 'opacity'){
// (count + destination) / 100 === (10 + 9) / 100 === 19 / 100 === 0.19
element.style[type] = (count + des) / 100;
}else{
element.style[type] = count + des + 'px';
}
}
})
}
// 获取标签对象
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, 'opacity', 1)
})
oDiv2.addEventListener('click', function () {
move(oDiv2, 'left', 200)
move(oDiv2, 'opacity', 1)
})
五、有多个属性时
- 问题:开启多个属性运动时,需要调用多次
- 原因:参数是固定的
- 解决:多个参数(功能还是单一),通过传递对象的形式
// 封装函数
// function move(element, type, target) {
function move(element, object) {
for(let key in object){
let type = key;
let target = object[key];
if(type === 'opacity') target *= 100;
let timer = setInterval(function(){
// 获取当前位置的值 兼容透明度
let count = type == 'opacity' ? window.getComputedStyle(element)[type]*100 : parseInt(window.getComputedStyle(element)[type]);
// 计算要移动的距离(目标 - 当前的值) / 10;
let des = (target - count) / 10;
// 大于0向上取整 小于0向下取整
des = des > 0 ? Math.ceil(des) : Math.floor(des);
// 判断是否需要继续移动
if(des === count){
// 此时说明移动到了 可以结束定时器了
clearInterval(timer);
}else{
// 此时说明还没有移动到, 需要继续移动
// 兼容透明度
if(type === 'opacity'){
element.style[type] = (count + des) / 100;
}else{
element.style[type] = count + des + 'px';
}
}
})
}
}
// 获取标签对象
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, {
opacity:0.3,
})
})
oDiv2.addEventListener('click', function () {
move(this,{
left:200,
opacity:1,
width: 200
})
})
六、运动函数真正结束
- 问题:没有办法完全捕获到运动函数的真正结束
- 原因:对象的每个属性,都会开启一个循环,每一个循环都会开启一个定时器,每个运动结束,就会结束一个定时器
- 解决:使用一个变最去记录,初始值可以给0,每次开启定时器的时候让变量自增,然后每次结束定时器的时候让变量自减
// 定义变量 存储数据
let num = 0;
// 封装函数
// function move(element, type, target) {
function move(element, object, fn = () => {}) {
for(let key in object){
let type = key;
let target = object[key];
if(type === 'opacity') target *= 100;
// 开启定时器 让变量自增 记录一下开启运动
num++;
let timer = setInterval(function(){
// 获取当前位置的值 兼容透明度
let count = type == 'opacity' ? window.getComputedStyle(element)[type]*100 : parseInt(window.getComputedStyle(element)[type]);
// 计算要移动的距离(目标 - 当前的值) / 10;
let des = (target - count) / 10;
// 大于0向上取整 小于0向下取整
des = des > 0 ? Math.ceil(des) : Math.floor(des);
// 判断是否需要继续移动
if(target === count){
// 定时器结束 变量--
num--;
if(num === 0){
console.log('函数结束了');
}
// 此时说明移动到了 可以结束定时器了
clearInterval(timer);
}else{
// 此时说明还没有移动到, 需要继续移动
// 兼容透明度
if(type === 'opacity'){
element.style[type] = (count + des) / 100;
}else{
element.style[type] = count + des + 'px';
}
}
})
}
}
// 获取标签对象
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, {
opacity:0.3,
})
})
oDiv2.addEventListener('click', function () {
move(this,{
left:200,
width: 200,
opacity: 1
})
})
七、回调函数
- 问题:我们现在想在运动函数结束之后做某些事,但是目前没法做到
- 原因:运动函数是异步的,所以会先执行同步代码
- 解决:在运动函数开始的时候,给他一个“锦囊”然后合诉运动函数,在运动完了的时候,才能打开“锦囊”
回调函数
- 回调函数(本质上就是一个函数),将函数A以实参的形式传递给函数B,然后函数B在函数内部以形参的方式调用函数A,此时函数A可以称为函数B的回调函数
- 回调函数一般用于异步代码的封装
// 定义变量 存储数据
let num = 0;
// 封装函数
// function move(element, type, target) {
function move(element, object, fn = () => {}) {
for(let key in object){
let type = key;
let target = object[key];
if(type === 'opacity') target *= 100;
// 开启定时器 让变量自增 记录一下开启运动
num++;
let timer = setInterval(function(){
// 获取当前位置的值 兼容透明度
let count = type == 'opacity' ? window.getComputedStyle(element)[type]*100 : parseInt(window.getComputedStyle(element)[type]);
// 计算要移动的距离(目标 - 当前的值) / 10;
let des = (target - count) / 10;
// 大于0向上取整 小于0向下取整
des = des > 0 ? Math.ceil(des) : Math.floor(des);
// 判断是否需要继续移动
if(target === count){
// 定时器结束 变量--
num--;
if(num === 0){
// 调用回调函数
fn()
console.log(num)
}
// 此时说明移动到了 可以结束定时器了
clearInterval(timer);
}else{
// 此时说明还没有移动到, 需要继续移动
// 兼容透明度
if(type === 'opacity'){
element.style[type] = (count + des) / 100;
}else{
element.style[type] = count + des + 'px';
}
}
})
}
}
// 获取标签对象
const oDiv1 = document.querySelector('.box1');
const oDiv2 = document.querySelector('.box2');
// 调用函数
oDiv1.addEventListener('click', function () {
move(oDiv1, {
opacity:0.3,
})
})
oDiv2.addEventListener('click', function () {
move(this,{
left:200,
width: 200,
opacity: 1
},() =>{
console.log('运动结束,背景色改为royalblue');
this.style.backgroundColor = 'royalblue';
})
})