目录
- JavaScript 运动原理
- 运动基础
- 简单运动的封装
- 淡入淡出
- 不同属性的设置
- 多属性值同时运动
- 运动回调,链式运动
- 缓冲运动
- 加入缓冲的运动框架
- 案例1 多图片展开收缩
- 运动的留言本
JavaScript 运动原理
运动基础
- 在JavaScript中,如何让一个页面元素动起来?
首先,我们需要了解的是,在JavaScript中如何让一个页面元素动起来。
我们先来实现一个简单的功能,当我们点击按钮之后,让一个元素动起来。并且到达500的边界之后立刻停止下来。
在上面的代码中,我们点击按钮之后,元素已经可以直接进行移动,但是却存在一个问题,什么问题呢?
当我们点击按钮之后,元素始终以10px的匀速进行运动,到达500的临界然后停止。 但是我们的问题是,速度可能会变,例如将速度变为7px,就不能够
准确的到达500的临界值。
例如:
出现这种情况的原因是因为运动的临界值必须能够被运动的速度(也就是oDiv.offsetLeft + 7 + 'px',表示每次执行移动的距离)整除。
上面的代码当中, 因为临界值不能够被速度整除,所以,最终元素始终达到不了临界值,那么元素就没有办法在到达临界值时停止。
同时在上面的代码中的另外一个问题是,当我们每点击一次运动按钮,元素的速度就会变得更快,原因很简单,就是我们设置的定时器发生了累加。
那么该如何解决定时器累加的问题呢?
我们可以在每次开始运动之前先清楚一次定时器。
oBtn.onclick = ()=>{
/*
* 为了防止定时器累加,在每次开始定时器之前,先清楚掉一个定时器
* */
clearInterval(iTimer);
iTimer = setInterval(()=>{
// 点击按钮之后,让div的位置在当前的基础之上每次增加10px的距离
// oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; // 虽然此代码可以让div动起来,但是我们需要div在运动之后到达某个边界就立刻停止,所以需要将此句代码改为一个判断
if (oDiv.offsetLeft === 500) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDiv.style.left = oDiv.offsetLeft + 7 + 'px'; // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
}
},30);
};
总结:在上面的代码中,是我们一般让一个元素运动起来的基本流程。下面进行一个简单的总结:
- 首先是存在的问题:处于匀速运动的元素没有办法进行在不整除的情况下在临界点停止。
- 在上面的代码中,可以将整个过程大致分为三个步骤:
- 清除定时器,保证只有一个定时器在执行
- 开启定时器
- 开始运动(需要同时加入一个判断,以便在需要的时候或者满足某个要求时停止运动)
简单运动的封装
为了让我们上面的代码可以具备更高的复用性,下面我们把上面的代码进行一个简单的封装。
示例:
在上面的代码中,我们将运动相关的内容放到了一个函数startMove
中,并且调用了这个函数,下面来根据这个函数进行案例的开发。
案例1:分享到功能
首先,先来实现基本的功能:
分享到
上面的代码中,我们鼠标移入,元素出现。鼠标移出,元素消失。
下面我们来使用我们的startMove函数,给元素出现和消失加上一个过渡的效果。
我们的startMove函数如下:
function startMove() {
clearInterval(iTimer);
iTimer = setInterval(()=>{
if (oDiv.offsetLeft === 500) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
}
},30);
}
我们想要在分享到
功能里使用这个函数,我们需要对我们的函数根据分享到的需求进行一定程度的更改。
首先是,将我们函数中的oDvi
更改为oDiv1
。
其次是我们分享到案例的需求在鼠标移入时需要将元素逐渐的显示,而鼠标移出时,需要将元素逐渐的隐藏。所以我们需要将startMove
函数创建两个
,并且当鼠标移出时,速度应该将函数中的+10
变为-10
。
当然,也别忘了去更该元素边界的值。当移出时,边界为0,移入时,边界为-100.
如下:
鼠标移入时调用的startMove1函数:
function startMove1() {
clearInterval(iTimer);
iTimer = setInterval(()=>{
if (oDiv1.offsetLeft === 0) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDiv1.style.left = oDiv.offsetLeft + 10 + 'px'; // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
}
},30);
}
鼠标移出时调用的startMove2函数:
function startMove() {
clearInterval(iTimer);
iTimer = setInterval(()=>{
if (oDiv1.offsetLeft === -100) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDiv1.style.left = oDiv.offsetLeft - 10 + 'px'; // 一旦将每次的移动距离变为7px ,那么将不能停的下来.元素会一直运动
}
},30);
}
我们先来将这两个函数放在代码中,进行测试。
分享到
上面的代码中,我们通过创建两个startMove函数,并且对相应的参数进行修改,从而实现了给我们的分享到功能添加了过渡效果。
进一步升级:
当然,我们上面的代码中使用的函数其实是非常不灵活的,所以我们下面要做到事就是对之前的函数进行升级,从而让我们的函数具备更强的实用性。
首先我们再回过头来看下我们刚才写的两个函数,你会发现,大部分的代码其实都是相同的,只有个别的值是不同的,例如元素移动的边界,例如元素
单位时间内移动的距离。
我们将上面的两个函数合并成一个函数,只需要将不一样的值提取出来当做参数即可。
下面是合并之后的函数:
function startMove(iTarget,iSpeed) {
clearInterval(iTimer);
iTimer = setInterval(()=>{
if (oDiv1.offsetLeft === iTarget) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
}
},30);
}
上面的函数升级完成之后,我们在重新的将这个函数应用到我们的分享到功能的代码当中去。
分享到
上面的代码中,我们顺利的通过我们的startMove函数给分享到功能添加了过渡的效果。
图片的淡入淡出效果:
下面我们再来看另外的一个效果,图片的淡入淡出
,还是通过我们上面定义好的startMove
函数来实现效果。
首先,我们上面的函数当中,只是针对oDiv1
,为了让我们的函数可以处理任意的元素,我们将oDiv1
替换成函数的形参。
例如:
function startMove(oDom,iTarget,iSpeed) {
clearInterval(iTimer);
iTimer = setInterval(()=>{
if (oDom.offsetLeft === iTarget) {
// 清除定时器
clearInterval(iTimer);
}else { // 没有到达边界才能继续运动
oDom.style.left = oDom.offsetLeft + iSpeed + 'px';
}
},30);
}
上面的代码当中,我们将函数操作的元素提取出来,变成了函数的形参,这样做之后,我们就可以让我们的函数针对任意的元素。
淡入淡出
下面的任务就是让我们的函数来处理图片的淡入淡出问题。
首先我们先来看下js当中的一个普遍情况,就是精度问题。
JavaScript精度问题
在js当中,关于小数的运算一向都是不够准确的。
例如:
alert(0.2 + 0.1);// 预期值 0.4 实际值: 0.30000000000000004
alert(0.2 + 0.7);// 预期值: 0.9 实际值: 0.8999999999999999
在上面的代码中,我们可以发现,在js当中的小数运算,精度是存在问题的,并不是很精确。
为什么要说到透明度的问题呢?
因为我们在设置透明度(opacity)的时候,还要考虑到兼容性的问题,另外一种写法(ie)是filter:alpha(opacity=30)
,里面的具体的值是正常的
透明度100,例如,我们正常设置透明度时,值为0.3
,那么filter
里面的值就是30
。而因为JavaScript中的小数计算不够精准,所以我们在
后面的函数的封装中将采用整数的形式,也就是正常的透明度小数值100的结果。
知道了上面的内容,我们来正式的写一下代码,首先先来引入一张图片:
在上面的代码中,我们已经找到图片元素,并且给图片元素绑定了事件以及事件处理函数,下面我们再来更改一下我们之前的startMove函数,让它专门
能够针对当前案例。
function startMove(oDom,iTarget,iSpeed) {
let iTimer = null;
clearInterval(iTimer);
let iCur = 0; // 用这个变量来存储透明度处理的值
iTimer = setInterval(()=>{
// 为了让值更加的精确,将结果四舍五入
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
if (iCur === iTarget) { // 判断是否等于目标的透明度
clearInterval(iTimer);
}else {
// 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}
},30);
}
在上面的代码中,我们为了获取图片本身的透明度,我们使用了一个css函数,这是一个我们自定义的函数,目的是为了获取元素的属性。
例如:
/**
* css 函数,可以用来获取元素的css属性
* 可以兼容ie和普通的浏览器
* */
function css(obj,attr) {
if (obj.currentStyle) {
return obj.currentStyle[attr];
}else {
return getComputedStyle(obj,false)[attr];
}
}
我们将startMove函数和css函数应用到上面的img案例当中,整体代码如下:
上面的代码中,我们通过更改的startMove函数和css函数实现了我们的需求,图片淡入淡出。
不同属性的设置
函数再升级:针对元素的不同属性效果
上面我们实现了分享到和图片的淡入淡出功能,我们为了实现功能,对我们的函数进行更改。而如果一个网页中同时存在着分享到和图片淡入淡出,或者
说在一个网页中同时存在需要运动的元素和需要淡入淡出的元素,根据我们上面的模式,只能在网页中同时设置两个函数:
分享到
我们发现,在上面的代码中,网页里同时存在分享到和图片淡入淡出的两个功能。
而且我们发现startMove1和startMove2两个函数的相似代码度非常高,那么我们是否可以将两个函数进行合并呢?
合并函数的第一步,找到两个函数当中不同的地方,然后将其替换成函数的参数。
运动属性的不同:
经过比对之后,我们可以发现,在上面的两个函数当中,存在一个不同之处,就是运动属性的不同,所以,我们可以再原本函数的基础上再来提取一个运动属性
。
那么更改之后的函数的调用方式就变成了类似下面这种:
// 让元素移动
startMove(this,'left',0,10); // this 运动的元素 left 运动的属性 0 目标 10 速度
// 让元素透明度发生变化
startMove(this,'opacity',30,-10); // this 变化的元素 opacity 设置的属性 30 目标 -10 单位变化的值
当我们弄清楚了函数的调用方式之后,就可以修改我们的函数了。
首先,可以先将我们之前的iTimer
变量设置到oDom
这个对象身上。
function startMove(oDom,attr,iTarget,iSpeed) {
clearInterval(oDom.iTimer);
// ....
oDom.iTimer = setInterval(()=>{
// code ...
};
};
我们接下来需要对设置的属性做一下判断,在css当中,透明度的设置需要特殊处理,其他的属性正常设置值就好。
oDom.iTimer = setInterval(()=>{
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
};
当我们对属性做完初步的判断之后,还需要对赋值进行更具体的判断,代码如下:
if (iCur === iTarget) { // 判断当前值是否等于目标值,
clearInterval(iTimer); // 如果等于,则清除定时
}else { // 如果不等于,就再来具体判断
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
完整的函数如下:
/*
* oDOM 要操作的元素
* attr 要操作的属性
* iTarget 变化的目标值
* iSpeed 变化的速度
*/
function startMove(oDom,attr,iTarget,iSpeed) {
clearInterval(oDom.iTimer);
let iCur = 0; // 用这个变量来存储透明度处理的值
oDom.iTimer = setInterval(()=>{
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
if (iCur === iTarget) { // 判断是否等于目标的透明度
clearInterval(oDom.iTimer);
}else {
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
},30);
}
/**
* css 函数,可以用来获取元素的css属性
* 可以兼容ie和普通的浏览器
* */
function css(obj,attr) {
if (obj.currentStyle) {
return obj.currentStyle[attr];
}else {
return getComputedStyle(obj,false)[attr];
}
}
将这个函数应用到网页当中,分享到和图片的淡入淡出效果都可以直接调用。
分享到
在上面的案例当中,我们已经能够实现一个页面当中多个元素同时调用一个函数进行运动。
下面我们再来将我们的函数进行升级,升级成,一个元素可以通过函数进行多值同时运动。
多属性值同时运动
多值同时运动:
例如,在网页当中存在一个div,然后当我们点击这个div的时候,让宽度变为200px,高度也变为200px。
首先,我们先来完成页面的基本布局:
设置完成基本样式之后,我们再来把基本的js代码实现:
let oDiv1 = document.getElementById('div1');
oDiv1.onclick = function(){
};
function startMove(oDom,attr,iTarget,iSpeed) {
clearInterval(oDom.iTimer);
let iCur = 0; // 用这个变量来存储透明度处理的值
oDom.iTimer = setInterval(()=>{
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
if (iCur === iTarget) { // 判断是否等于目标的透明度
clearInterval(oDom.iTimer);
}else {
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
},30);
}
在上面的代码中,我们给div元素绑定了单击事件,下面我们再来处理一下moveStart
函数。
为了能够一次性操作多个值,我们可以将操作的属性以json
的形式来传入。
// 以下面的方式进行传值
startMove(this,{ // this 操作的元素
width:200, // 操作的属性1
height:200 // 操作的属性2
},10); // 速度
为了实现上面的传递值的方式,函数的参数需要变成下面的样式:
function startMove(oDom,json,iSpeed){
// code ...
}
在更改具体的代码之前,我们先来回顾之前的代码
我们之前的代码在设置属性时,采用的是下面的写法:
oDom.iTimer = setInterval(()=>{
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
// code ...
},30);
上面的代码是我们之前的代码,但是为了实现多值同时变化,需要将上面的判断代码更改成如下的样子:
function startMove(oDom,json,iSpeed) {
clearInterval(oDom.iTimer);
let iCur = 0; // 用这个变量来存储透明度处理的值
oDom.iTimer = setInterval(()=>{
// 因为传入的参数是json,为了逐一操作,需要开启循环
for(let attr in json) {
// 设置目标值
let iTarget = json[attr]; // 当前属性要运动的值就是目标值
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
if (iCur === iTarget) { // 判断是否等于目标的透明度
clearInterval(oDom.iTimer);
}else {
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
}
},30);
}
function css(obj,attr) {
if (obj.currentStyle) {
return obj.currentStyle[attr];
}else {
return getComputedStyle(obj,false)[attr];
}
}
将更改之后的函数放在单击事件处理函数里执行。
我们上面的代码运行经过测试之后,点击div,已经可以实现宽度和高度同时变化。
但是我们上面的代码却存在一个问题。
例如,将宽度变为200,高度变为300.
startMove(this,{width:200,height:300},10);
这个时候,我们发现点击元素之后,宽度变为了200,但是高度却没有变成指定的300。原因也很简单,多个变化属性的速度是一致的,当其中一个属性
到达目标值时,定时器就会被清除,这个时候个别属性就不再发生变化。
所以这个时候我们就要思考一个问题,到底什么时候停止定时器?
答案其实很简单,就是当所有属性全部达到目标值时才停止运动。
我们再回来分析一下之前的代码,我们每一次循环,其实都相当于变化了一个属性,在循环的过程中,其中一个属性变化的过程中并没有办法知道另外一个
属性是否完成了变化。
所以我们应该在循环之外判断,当循环一轮之后,我们就立刻在循环之外看下每个属性是否到达了目标值。
首先,我们在代码之前设置一个变量,存储一个布尔值,用来表示是否达到目标点。
在每次进入定时器时,都把属性变为true,然后在后续的判断中,如果属性没到达目标点,就把这个变量的值变为false。
oDom.iTimer = setInterval(()=>{
// 设置变量存储初始值
let iBtn = true;
// code ...
for(let attr in json) {
// code ...
// 将原来的iCur === iTarget 变为下面的判断
if (iCur !== iTarget) {
// 如果其中的一个属性没到,就将iBtn 变为false
iBtn = false;
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
}
// 通过判断iBtn的值来决定清除定时器
if (iBtn){
clearInterval(oDom.iTimer);
}
},30}
完整的函数代码如下:
function startMove(oDom,json,iSpeed) {
clearInterval(oDom.iTimer);
let iCur = 0; // 用这个变量来存储透明度处理的值
oDom.iTimer = setInterval(()=>{
// 设置一个变量,用来存储状态,表示元素是否到达目标值
let iBtn = true;
// 因为传入的参数是json,为了逐一操作,需要开启循环
for(let attr in json) {
// 设置目标值
let iTarget = json[attr]; // 当前属性要运动的值就是目标值
// 进行操作判断
if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理
iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100
}else { // 其他的值可以直接设置
iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数
}
if (iCur !== iTarget) { // 判断是否等于目标的透明度
iBtn = false;
if(attr === 'opacity'){ // 对透明度的设置需要特殊处理
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
}else { // 其他属性值的设置
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
}
// 判断iBtn是否为true,为true表示元素已经到达了目标值
if (iBtn){
clearInterval(oDom.iTimer);
}
},30);
}
function css(obj,attr) {
if (obj.currentStyle) {
return obj.currentStyle[attr];
}else {
return getComputedStyle(obj,false)[attr];
}
}
运动回调,链式运动
我们在实际的运动当中,存在一种情况,就是我们需要当一个属性运动完成之后再去执行另外一个属性的变化。
想要实现这种功能,我们需要将我们的代码改成回调的形式。
首先,在我们的函数中,加入一个回调函数的参数
startMove(oDom,json,iSpeed,fn){}
至于调用的时机,我们可以放在一个属性动画完成之后再去执行回调,并且同时需要判断,是否存在回调,如果存在,在执行回调。
// 判断iBtn是否为true,为true表示元素已经到达了目标值
if (iBtn){
clearInterval(oDom.iTimer);
fn && fn.call(oDom); // 通过call改变this指向,方便我们后续的回调函数的调用。
}
下面我们来尝试一下,div点击之后,先将宽度变为200,宽度变化完成之后,再讲高度变为300.
上面的代码中 ,我们顺利的实现了代码的回调函数以及多属性延迟调用的功能。
缓冲运动
上面的代码中,我们已经基本实现了运动框架的基本构建。但是我们在实际使用的过程中,还存在着一种速度缓冲的现象,什么叫速度缓冲呢?简单的说
就是速度的逐渐变慢或者逐渐变快。
在本案例中将以速度逐渐变慢为例,来完成函数的再次升级。速度的逐渐变慢,也有人称之为摩擦运动。
首先,我们先来完成一个基本的demo。
我们先来在网页中放置一个按钮,一个div。当我们点击按钮的时候,让div发生运动。
上面的代码中,我们点击按钮之后,就会触发单击事件的事件处理函数。
下面来针对这个事件处理函数进行运动处理 。
// 获取按钮和元素
let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1");
let iTimer = null;
let iSpeed = 50; // 定义一个初始速度
// 给按钮绑定单击事件
oBtn.onclick = function () {
/*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/
clearInterval(iTimer);
iTimer = setInterval(function(){
// 进行具体的速度判读
if(oDiv1.offsetLeft === 500 ){
clearInterval(iTimer);
}else {
oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
}
},30);
};
上面的代码完毕之后,我们点击按钮之后元素以匀速的形式到达了目标点。
下面我们来处理一下如何让我们的元素以摩擦减速的效果到达目标点。
此时我们应该知道,我们希望得到的减速运动效果,特征是越接近于目标点,速度应该越慢。
let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1");
let iTimer = null;
oBtn.onclick = function () {
/*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/
clearInterval(iTimer);
let iSpeed = 0;
iTimer = setInterval(function(){
// 处理速度
iSpeed = (500 - oDiv.offsetLeft) / 8;
// 当元素的位置超过了目标位置,那么元素运动就始终到达不了目标位置,所以需要下面这种形式的判断。
iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); //速度如果大于0,向上取整,如果小于0,向下取整
// 进行具体的速度判读
if(oDiv1.offsetLeft === 500 ){
clearInterval(iTimer);
}else {
oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px';
}
},30);
};
在上面的代码中,为了实现我们缓冲运动
的需求,我们通过距离越短,速度越慢
的原理实现了渐变的速度,同时,为了防止速度最终没有
办法达到目标值,所以使用了取整的方式。
下面是案例的完整代码:
上面我们弄清楚了缓冲的原理之后,再来将缓冲加到我们之前封装的运动框架当中。
加入缓冲的运动框架
我们上面了解了缓冲运动的原理,下面来进行一个简单的总结。
缓冲运动
- 缓冲运动与摩擦力的区别: 能够精确的到达目标位置。
- 同时,速度由距离决定。
- 速度 = (目标值 - 当前值 ) / 缩放系数
- 值需要取整
function startMove(oDom, json, fn) {
clearInterval(oDom.iTimer);
var iCur = 0;
var iSpeed = 0;
oDom.iTimer = setInterval(function() {
var iBtn = true;
for ( var attr in json ) {
var iTarget = json[attr];
if (attr === 'opacity') {
iCur = Math.round(css( oDom, 'opacity' ) * 100);
} else {
iCur = parseInt(css(oDom, attr));
}
iSpeed = ( iTarget - iCur ) / 8;
iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
if (iCur !== iTarget) {
iBtn = false;
if (attr === 'opacity') {
oDom.style.opacity = (iCur + iSpeed) / 100;
oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')';
} else {
oDom.style[attr] = iCur + iSpeed + 'px';
}
}
}
if (iBtn) {
clearInterval(oDom.iTimer);
fn && fn.call(oDom);
}
}, 30);
}
function css(oDom, attr) {
if (oDom.currentStyle) {
return oDom.currentStyle[attr];
} else {
return getComputedStyle(oDom, false)[attr];
}
}
我们来通过我们的运动框架来让元素发生一个缓冲运动。
运动框架下的缓冲运动
案例1 多图片展开收缩
当我们鼠标放在图片上的时候,图片会放大,当我们的鼠标移开图片的时候,图片缩小。
首先我们先来把案例的基本布局完成:
Title
上面的代码,我们完成了基本的布局,下面我们要做的是,让元素居中放大。
需要注意的是,如果仅仅直接更改图片的宽度和高度,并不能实现居中布局,因为在网页中,元素变化的点是以左上角开始的。所以我们想要实现
居中放大,就同时还需要使用定位,更改元素的定位(left,top),才能让元素实现居中放大。如果图片放大一倍,那么位移宽度和高度的一半。
并且元素一定是要定位的。但是我们在基本的布局时,应该采用浮动来进行定位,后面再通过js动态的将元素变为定位。
首先为了让我们的元素在通过js之后又相对参考的点,所以说需要给图片的父级,也就是ul设置一个相对定位的属性:
#list {position: relative;}
接下来我们来实现一下js的效果:
let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');
for(let i=0;i
上面的代码中,当js开始执行之后,图片就已经从浮动布局变成了定位布局,但是从视觉上看却没有任何的改变,这正是我们想要的。
接下来我们来完成具体的变化效果。
把之前封装的动画框架存储到一个独立的js文件中,命名为move.js
,并且引入这个js。引入之后调用我们的js文件并且传入参数。
let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');
for(let i=0;i
上面的代码中,我们调用函数之后,当我们的鼠标放在图片身上的时候,图片已经可以发生变化,但是发现图片的层级发生了问题,这个时候我们再来处理下层级的问题。
更改之后的代码如下:
let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');
let zIndex = 1; // 设置一个变量用来存储层级信息
for(let i=0;i
设置好层级信息之后,我们经过测试,再一次的发现了一个问题,当我们的鼠标不断悬浮在相同一个元素的身上,发现元素被我们的鼠标赶走了。
原因是,当我们的鼠标第二次悬浮元素身上的时候,元素再一次的在之前的基础上进行减法,所以才会出现上面的现象。
解决方案:让我们的元素每次-50不再是在现有位置上减,而是在初始的位置上进行减。
更改之后的代码如下:
let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');
let zIndex = 1; // 设置一个变量用来存储层级信息
let arr = []; // 创建一个数组,用来存储元素位置的初始信息
for(let i=0;i
经过检测之后,没有问题,那么下面再来绑定鼠标移出的事件以及事件处理函数即可。
完整代码如下:
Title
运动的留言本
什么是运动的留言本呢?其实非常简单,就是当我们在输入域中输入内容之后,点击添加,内容会在右侧的留言框内一点一点的出现。
首先,我们先来实现基本的布局。
运动的留言本
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
- aaaaaaaa
实现了基本的布局之后,我们先来实现js的基本功能,就是点击留言,添加留言内容。
js代码如下:
// 先来获取页面的元素
let oBtn = document.getElementById("btn");
let oContent = document.getElementById("content");
let oUl = document.getElementById('ul1');
oBtn.onclick = function () {
// 创建节点
let oLi = document.createElement('li');
oLi.innerHTML = oContent.value;
if(oUl.children[0]) {
oUl.insertBefore(oLi,oUl.children[0]); // 如果里面存在内容,就把消息插入到最上面
}else {
oUl.appendChild(oLi);
}
};
实现了发布内容的功能,我们再来实现让内容逐渐的出现。
oBtn.onclick = function () {
// 创建节点
let oLi = document.createElement('li');
oLi.innerHTML = oContent.value;
if(oUl.children[0]) {
oUl.insertBefore(oLi,oUl.children[0]); // 如果里面存在内容,就把消息插入到最上面
}else {
oUl.appendChild(oLi);
}
let iHeight = parseInt( css(oLi,'height')); // 获取元素实际的高度并且取整
oLi.style.height = '0px';
oLi.style.opacity = '0';
oLi.style.filter = 'alpha(opacity=0)';
startMove(oLi, {
height : iHeight,
opacity : 100
});
};
完整代码如下:
运动的留言本