你应该掌握的JavaScript高阶技能(一)

你应该掌握的 JavaScript 高阶技能(一)之原生实现轮播图

本文内容参考 pink 老师的素材与视频讲解,特别鸣谢❤️

可能有些朋友有疑问了,轮播图制作也算高阶技能嘛?当然,轮播图对于前端朋友要求扎实的基础,内容涉及到 js基础、DOM BOM等等内容,学会制作轮播图,能让您更进一步地掌握 JavaScript 的知识,同时作为第一期,有个好开头,让大家更好上手,好啦现在开始!


轮播图在网页中是无处不在,轮播图在网页中起到非常重要的作用,我们可以发现,点击轮播图的(图示)按钮,可以切换图片,底部(图示)小矩形同样可以有切换图片的效果,还有自动切换图片的功能。对于一名前端同学,能够使用原生实现轮播图是必备技能!

轮播图示

在这里插入图片描述

我们将图片进行刨析,它的原型应该长这样,如图所示:
在这里插入图片描述

基本流程:

可视窗口中(绿色空心矩形)只有一张图片,图片左右两侧有两个方向为左和右的按钮,点击可以进行图片切换,发生滚动效果。


页面结构要点与注意事项

1.使用元素 ul 包裹所有图片

2.按钮

3.可视窗口底部位置有小圆点 与图片一一对应

4.可视窗口不动,滚动区域为 ul

5.需要使用动画效果

6.图片不是竖排列 而是横排列 让所有的li元素在一行上

tips : 页面结构问题是 css 问题,样式不过多讲解,重点是 js实现部分!


需求

需求一:鼠标离开轮播图,按钮消失,鼠标经过,按钮出现

需求二:点击按钮可以切换图片

需求三:图片切换,底部小圆圈一同变化,小圆圈和图片一一对应

需求四:点击底部小圆圈 ,切换到对应图片

需求五:鼠标离开,自动播放下一张图片,鼠标进入轮播图,取消自动播放下一张图片。


外部引入 js 文件,取名 index.js

完成需求一:鼠标离开轮播图,按钮消失,鼠标经过,按钮出现

  • 页面加载完毕所有界面之后再执行
  • 先隐藏按钮 --> css设置 display: none
  • 获取元素,设置相关事件监听。
window.addEventListener('load', function () {
	//获取元素
    let arrow_l = document.querySelector('.arrow-l');//左按钮
    let arrow_r = document.querySelector('.arrow-l');//左按钮
	let focus = document.querySelector('.focus');//可视窗口
    focus.addEventListener('mouseenter',function(){
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
    });
    focus.addEventListener('mouseleave',function(){
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
    })
})

先完成需求三 :图片切换,底部小圆圈一同变化,小圆圈和图片一一对应

  • 例如 :有 n 个图片,就有 n 个小圆圈
  • 小圆圈是动态生成
  • 首先得到 ul 里面图片数量(图片放在 li 里面 所以就是 li 的个数)
  • 利用循环动态生成小圆圈(放入 ol
  • 创建节点 createElement('li') ,插入节点 ol.appendChild('li')
  • 先删掉 ol 中所有的 li ,默认 ol 中第一个li设置类名为 current,可 变为实心,表示当前选中
//todo... focus
//根据图片动态生成小圆圈
let ul = focus.querySelector('ul');
let ol = focus.querySelector('.circle');
for(var i = 0; i < ul.children.length; i++){
    //创建li元素
    //将li元素插入ol
    let li = document.createElement('li');
    ol.appendChild(li);
}
//ol中第一个li设置类名为current
ol.children[0].className = 'current';
//todo...something

完成小圆圈的相关操作

  • 点击小圆圈,当前小圆圈为实心,其他为空心(排他思想)
  • 排他思想是从pink老师所学,非常实用,干掉所有人,留下我自己。
  • 意思是点击当前小圆点,添加 current 类,其余的小圆圈就移除 current 类。
  • 生成小圆圈的同时,可以绑定点击事件监听。
//todo...
for(let i = 0; i < ul.children.length; i++){
    //创建li元素 同时将li元素插入ol
    let li = document.createElement('li');
    ol.appendChild(li);
    li.addEventListener('click',function(){
        //排他思想
        //点击 先清除所有li的current类名
        for(let i = 0; i < ol.children.length; i++){
            ol.children[i].className = '';
        }
        //再将被点击的li设置current类名
        this.className = 'current';
    });
}
//todo...

需求二 | 需求三 :点击小圆圈可以实现图片滑动效果

需要使用 animate动画函数

动画原理

  • 获取盒子当前位置
  • 让盒子在当前位置加上 n 个移动距离
  • 利用定时器不断重复这个操作
  • 添加一个结束定时器的条件
  • 此元素需要添加定位,才能 element.style.left 生效
//html请您自行实现 只展示js部分
var div = document.querySelector('div');
var timer = setInterval(function () {
    if (div.offsetLeft >= 400) {
        //停止动画 本质是停止定时器
        clearInterval(timer);
    }
    div.style.left = div.offsetLeft + 1 + 'px';
}, 30);

简单动画函数封装

需要 2个参数 【动画对象和移动到的距离】

//简单动画函数封装 obj目标对象 target目标位置
function animate(obj, target) {
    var timer = setInterval(function () {
        if (obj.offsetLeft >= target) {
            //停止动画 本质是停止定时器
            clearInterval(timer);
        }
        obj.style.left = obj.offsetLeft + 1 + 'px';
    }, 30);
}
//如果有100个对象开启100个内存空间给变量timer
//而且不同对象都使用timer,具有歧义

改进 给不同对象添加指定不同的定时器

如果多个元素都使用这个动画函数,每次都要使用 let 或 var 声明定时器,我们可以给不同的元素使用不同的定时器,核心原理,利用 js 是一门动态语言,可以很方便的给当前对象添加属性。

function animate(obj, target) {
	clearInterval(obj.timer);
	obj.timer = setInterval(function () {
	    if (obj.offsetLeft >= target) {
	        //停止动画 本质是停止定时器
	        clearInterval(obj.timer);
	        // return;
	    } else {
	        obj.style.left = obj.offsetLeft + 1 + 'px';
	    }
	}, 30);
}

//todo...
btn.addEventListener('click', function () {
    animate(element, 300);
});
//如果点击次数越多 速度越快 开启太多定时器
//解决方法 让元素的定时器只有一个

缓动动画原理

需要做到轮播图的效果需要掌握缓动动画原理

缓动动画就是让元素运动速度有所变化。最常见的就是让速度慢慢停下来

思路

  • 让盒子每次移动的距离逐渐变小,速度就会逐渐变小直到 0
  • 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离(步长)
  • 停止的条件:让当前盒子位置等于目标位置就停止定时器
// 缓动动画函数封装obj目标对象 target 目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
// 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
    // 先清除以前的定时器,只保留当前的一个定时器执行
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
        //步长值写到定时器的里面
        //把我们步长值改为整数 不要出现小数的问题
        // var step = Math.ceil((target - obj.offsetLeft) / 10);
		let step = (target - obj.offsetLeft) / 10;
		step = step > 0 ? Math.ceil(step) : Math.floor(step);
		if (obj.offsetLeft == target) {
		    // 停止动画 本质是停止定时器
		    clearInterval(obj.timer);
		}
		// 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
		obj.style.left = obj.offsetLeft + step + 'px';
    }, 15);
}
// 匀速动画 就是 盒子是当前的位置 +  固定的值 10
// 缓动动画就是  盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)

给动画函数添加回调函数

回调函数原理:函数可以作为一个参数,将这个函数作为参数传到另一个函数里面,当动画函数执行完成之后,再执行传入作为参数的函数,这个过程叫做回调

//animate.js
function animate(obj, target,callback) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
        //todo...
		if (obj.offsetLeft == target) {
			// 停止动画 本质是停止定时器
			clearInterval(obj.timer);
			//回调函数写道定时器结束里面
			if (callback) {
			    //调用函数
			    callback();
            }
            //或者写成
            //callback && callback();
        }
        //todo...
    }, 15);
}

完成 animate.js 后,引入animate.js

注意事项

(1)index.js 依赖于 animate.js animate.js 要写到 index.js 上面

(2)可视窗口不动,图片移动 ul 要设置定位,是 ul 动,不是 li 动,因为 ul包裹着 li元素。

(3)滚动图片的核心算法:点击某个小圆圈,图片滚动,小圆圈的索引号乘以图片宽度作为 ul 的移动距离,比如点击第一个小圆圈,索引号为 0,若图片长度为 200 px,图片不移动,如果点击的是第二个,移动 200 px。

//todo...
li.addEventListener('click',function(){
    for(let i = 0; i < ol.children.length; i++){
        ol.children[i].className = '';
    }
    this.className = 'current';
    // 点击小圆圈移动图片 移动 ul
    // animate(obj,target,callback);
    // target 移动距离 注意是负值
    // 先拿到图片宽度
    let focusWidth = focus.offsetWidth;
    console.log(focusWidth);
});
//todo...

此时需要知道小圆圈的索引号,在生成小圆圈的时候,设置自定义属性,可在点击时获取自定义属性(即为索引号)即可,然后可以使用 animate 函数

//todo...
for (let i = 0; i < ul.children.length; i++) {
        //创建li元素 同时将li元素插入ol
        let li = document.createElement('li');
    	//记录当前小圆圈的索引号,通过自定义属性
    	li.setAttribute('index',i);
        ol.appendChild(li);
        li.addEventListener('click', function () {
            //排他思想
            //点击 先清除所有li的current类名
            for (let i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';
            }
            //再将被点击的li设置current类名
            this.className = 'current';
            let index = this.getAttribute('index');
            let focusWidth = focus.offsetWidth;
            animate(ul,-index*focusWidth);
        });
}
//todo...

现在可以实现点击小圆圈能够准确定位图片的位置的功能!


完成需求四:点击右侧按钮一次 就让图片滚动一张

可以声明一个变量 num,点击一次,num++ ,让变量乘以图片的宽度,也就是ul滚动距离

//todo...
arrow_r.addEventListener('click',function(){
    num++;
    animate(ul, -num * focusWidth);
})
//todo...

现在还是有问题,会移动出图片所在区域(可视区域),而网页中轮播图都是使用图片无缝滚动原理可以将第一张图片复制一份到最后一张,如果图片已经是最后一张了,点击会进入复制的第一张的图片,然后会“不做动画的”跳入第一张。

arrow_r.addEventListener('click', function () {
    //如果走到最后复制的一张图片,此时需要将ul快速复原left改为0
    if(num == ul.children.length - 1){
        ul.style.left = 0;
        num = 0;
    }
    num++;
    animate(ul, -num * focusWidth);
})

改进

刚刚的做法是直接将第一张图片复制,可是真实的场景是我们不知道第一张图片具体是什么?所以让 js 帮我们复制第一张图片到最后一张。

  • 克隆第一张图片,克隆 ul 第一个 li ,使用 cloneNode()true 为深克隆,复制里面的节点,false 为浅克隆。
  • 添加 ul 最后一个节点,使用appendChild
//todo...
//克隆第一张图片(li)放到ul最后
let firstNode = ul.children[0].cloneNode(true);
ul.appendChild(firstNode);
//todo...

解释一下:为什么多了一个节点小圆圈个数不变 ?

因为克隆第一张图片的是在生成小圆圈的时候之后做的。


实现:点击右侧按钮,小圆圈一同跟随变化

  • 声明一个变量 circle 每次点击自增 1 ,左侧同理
  • 如果 circle 为 图片的总数量,需要置为 0
//todo...
let circle = 0;
//todo...
arrow_r.addEventListener('click', function () {
	//todo...
	//点击右侧按钮 小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放
    circle++;
    if(circle == ol.children.length){
        circle = 0;
    }
    //排他思想
    for (let i = 0; i < ol.children.length; i++){
        ol.children[i].className = '';
    }
    ol.children[circle].className = 'current';
    //todo...
})

bug小问题 :点击第三个小圆圈到第三张图片,再点击右侧按钮,居然是跳到第二张图片,为什么?

因为我们播放下一张是通过 num来控制的 跟小圆圈点击没有任何关系,简单理解,切换到第三张图片,索引号为 2 ,修改 num的值为 2 ,需要同步,一一对应。

//todo...
let num = 0;
li.addEventListener('click', function () {
	//排他思想
	//点击 先清除所有li的current类名
	for (let i = 0; i < ol.children.length; i++) {
	    ol.children[i].className = '';
	}
	//再将被点击的li设置current类名
	this.className = 'current';
	let index = this.getAttribute('index');
	//当我们点击某个li 将li的索引号给num
    circle = num = index;
	animate(ul, -index * focusWidth);
});
//todo...

现在是到下一张图片了,但是小圆圈回到上一个,不是到下一个,我们知道 circle是控制小圆圈的播放,将索引号也给 circle , 同步关系


左侧按钮实际上跟右侧按钮是一样的做法,逆向思考一下!

arrow_l.addEventListener('click', function () {
	if (num == 0) {
	    // ul.style.left = -(ul.children.length - 1)* focusWidth;
	    // num = ul.children.length - 1;
	    num = ul.children.length - 1;
	    ul.style.left = -num * focusWidth;
	}
	num--;
	animate(ul, -num * focusWidth);
	//点击右侧按钮 小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放
	circle--;
	if (circle < 0) {
	    circle = ol.children.length - 1;
	}
	//排他思想
	for (let i = 0; i < ol.children.length; i++) {
	    ol.children[i].className = '';
	}
	ol.children[circle].className = 'current';
})

左侧和右侧的按钮既然排他思想的代码是一样的,不如封装到一个函数里?

function circleChange() {
	//排他思想
	for (let i = 0; i < ol.children.length; i++) {
	    ol.children[i].className = '';
	}
	ol.children[circle].className = 'current';
}

代码优化

if (circle < 0) {
    circle = ol.children.length - 1;
}

//写成三元表达式
//todo...improve the code
circle = circle < 0 ? (ol.children.length - 1) : circle;

完成需求五:自动播放功能

  • 添加定时器 setInterval
  • 手动调用右侧按钮点击事件 element.click()
  • 鼠标经过可视窗口,停止定时器
  • 鼠标离开可视窗口,开启定时器
//todo...
var timer = setInterval(function(){
    //自动播放图片 -->  点击右侧按钮
    arrow_r.click();
},2000);
//todo...
focus.addEventListener('mouseenter', function () {
	//todo...
    clearInterval(timer);
    timer = null;//清除定时器变量
});

focus.addEventListener('mouseleave', function () {
	//todo...
    timer = setInterval(function(){
    //自动播放图片 -->  点击右侧按钮
    	arrow_r.click();
	},2000);
});

节流阀

  • 防止轮播图按钮连续点击造成播放过快
  • 节流阀的目的:当上一个函数动画内容执行完毕,再执行下一个函数动画,让事件无法连续触发
  • 设置一个变量 var flag = true
  • if(!flag){flag = false; do something}; 给函数“加锁”
  • 利用 animate 第三个参数传入函数(回调函数),动画函数执行完毕,设置 flag = true 给函数“解锁”
//节流阀
let flag = true;
arrow_r.addEventListener('click', function () {
	if(flag){
        flag = false;//关闭节流阀
        if (num == ul.children.length - 1) {
	    	ul.style.left = 0;
	    	num = 0;
		}
		num++;
		animate(ul, -num * focusWidth);
		//点击右侧按钮 小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放
		circle++;
		if (circle == ol.children.length) {
	    	circle = 0;
		}
		circleChange();
    }
});

animate 动画函数执行完毕,就打开节流阀,可以播放下一张图片

animate(ul, -num * focusWidth, function(){
    flag = true;//打开节流阀
});

左侧按钮操作同理,这里不再展示。


index.js 源码

window.addEventListener('load', function () {
    //获取元素
    let arrow_l = document.querySelector('.arrow-l');//左按钮
    let arrow_r = document.querySelector('.arrow-r');//左按钮
    let focus = document.querySelector('.focus');//可视窗口
    let focusWidth = focus.offsetWidth;
    let circle = 0;
    let num = 0;
    let flag = true;
    focus.addEventListener('mouseenter', function () {
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
        clearInterval(timer);
        timer = null;//清除定时器变量
    });

    focus.addEventListener('mouseleave', function () {
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
        timer = setInterval(function () {
            //自动播放图片 -->  点击右侧按钮
            arrow_r.click();
        }, 2000);
    });
    let ul = focus.querySelector('ul');
    let ol = focus.querySelector('.circle');
    for (let i = 0; i < ul.children.length; i++) {
        //创建li元素 同时将li元素插入ol
        let li = document.createElement('li');
        //记录当前小圆圈的索引号,通过自定义属性
        li.setAttribute('index', i);
        ol.appendChild(li);
        li.addEventListener('click', function () {
            //排他思想
            //点击 先清除所有li的current类名
            for (let i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';
            }
            //再将被点击的li设置current类名
            this.className = 'current';
            let index = this.getAttribute('index');
            //当我们点击某个li 将li的索引号给num circle
            circle = num = index;
            animate(ul, -index * focusWidth);
        });
    }
    //默认第一个ol中第一个li设置为 current
    ol.children[0].className = 'current';
    //克隆第一张图片(li)放到ul最后
    let firstNode = ul.children[0].cloneNode(true);
    ul.appendChild(firstNode);
    //点击右侧按钮 图片滚动一张
    arrow_r.addEventListener('click', function () {
        if (flag) {
            flag = false;//关闭节流阀
            if (num == ul.children.length - 1) {
                ul.style.left = 0;
                num = 0;
            }
            num++;
            animate(ul, -num * focusWidth, function () {
                flag = true;
            });
            //点击右侧按钮 小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放
            circle++;
            if (circle == ol.children.length) {
                circle = 0;
            }
            circleChange();
        }
    });

    arrow_l.addEventListener('click', function () {
        if (flag) {
            flag = false;
            if (num == 0) {
                // ul.style.left = -(ul.children.length - 1)* focusWidth;
                // num = ul.children.length - 1;
                num = ul.children.length - 1;
                ul.style.left = -num * focusWidth;
            }
            num--;
            animate(ul, -num * focusWidth, function () {
                flag = true;
            });
            //点击右侧按钮 小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放
            circle--;
            // if (circle < 0) {
            //     circle = ol.children.length - 1;
            // }
            circle = circle < 0 ? (ol.children.length - 1) : circle;
            circleChange();
        }
    })

    function circleChange() {
        //排他思想
        for (let i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        }
        ol.children[circle].className = 'current';
    }

    var timer = setInterval(function () {
        //自动播放图片 -->  点击右侧按钮
        arrow_r.click();
    }, 2000);
})

最终效果

在这里插入图片描述
这个 gif 生成图我还得研究研究🧐!

第一期你应该掌握的 JavaScript 高阶技能内容就这么多啦,如果本专栏内容不错的话,望您能关注🤞点赞👍收藏❤️一键三连!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值