源代码:code.juejin.cn/pen/7108191…
[本文适合小白,对于JS不是很清楚,听课听不懂的]
构造方法保持简单,实例化过程更清晰,只需获取核心元素而不处理复杂逻辑。
插件实现了类和功能的分离,将事件绑定和切换逻辑抽离出类定义,使类结构更简洁,增强代码的复用性和灵活性。
插件本质上就是将本可以在 class 内部定义的功能提取出来,作为独立的函数或模块,这样不仅减少了类的复杂性,还可以随时添加或移除,不影响 Slider 类核心。
通过 registerPlugins,可以同步注册多个插件,一行代码即可完成所有注册,使代码更简洁,核心功能与扩展功能互不干扰,从而提升了代码的模块化和可维护性。
- 自定义事件
slide用于传递照片索引idx,这样触发事件时,可以让按钮组接收idx,将对应的按钮设为选中状态。这样照片和按钮就能同步显示,实现了两者相对应的效果。
class Slider {
constructor(id, cycle = 3000) { // 定义一个构造方法,在进行实例化时需要传入两个参数,一个是容器id,一个是自动切换时间
this.container = document.getElementById(id); // 通过实例化时传入的id获取对应的DOM元素
this.items = this.container.querySelectorAll(
".slider-list__item, .slider-list__item--selected",
); // 取到所有的图片
this.cycle = cycle; // 取到实例化时传入的自动切换时间
}
registerPlugins(...plugins) { // 注册插件,列表展开符号(...)将所有传入的插件参数转换为一个数组
plugins.forEach((plugin) => plugin(this)); // 遍历每一个输入的插件,都进行注册
}
getSelectedItem() { // 获取当前选中的照片元素
const selected = this.container.querySelector(
".slider-list__item--selected",
);
return selected;
}
getSelectedItemIndex() { // 返回当前选中照片的索引
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx) { // 移动到索引为idx的照片的位置
const selected = this.getSelectedItem();
if (selected) {
selected.className = "slider-list__item";
}
const item = this.items[idx];
if (item) {
item.className = "slider-list__item--selected";
}
// 创建自定义事件"slide",事件在 DOM 树中向上冒泡,父级元素也可以监听此事件
const detail = { index: idx }; // 新建一个slide自定义事件,其中的bubbles是指DOM元素可以进行冒泡,其父元素可以进行接收
const event = new CustomEvent("slide", { bubbles: true, detail }); // detail是数据载体,说明slide里面有一个参数index,其值是idx
this.container.dispatchEvent(event); // 在 container 元素上触发"slide"事件,所有对该事件设置了监听的代码会被调用
}
slideNext() { // 滑到下一张照片,先获取索引,然后令索引值加1,调用slideTo函数
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious() { // 滑到上一张照片,先获取索引,然后令索引值减1,调用slideTo函数
const currentIdx = this.getSelectedItemIndex();
const previousIdx = (this.items.length + currentIdx - 1) %
this.items.length;
this.slideTo(previousIdx);
}
addEventListener(type, handler) { // 重写事件监听方法,输入一个event的type,以及对应的event
this.container.addEventListener(type, handler); // 容器进行监听
}
start() { // 开始自动切换函数,需要先清楚当前的计时器,然后设置一个cycle毫秒的计时器
this.stop();
this._timer = setInterval(() => this.slideNext(), this.cycle);
}
stop() { // 停止自动切换函数 清楚当前的计时器
clearInterval(this._timer);
}
}
function pluginController(slider) { // 插件控制器函数
const controller = slider.container.querySelector(".slide-list__control"); // 取到类为slide-list__control的DOM元素
if (controller) { // 如果取到的话
const buttons = controller.querySelectorAll(
".slide-list__control-buttons, .slide-list__control-buttons--selected",
); // 从该DOM元素中取到所有的按钮
controller.addEventListener("mouseover", (evt) => { // 事件监听,如果鼠标移动上来 就切换到对应idx的照片
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener("mouseout", (evt) => { // 鼠标挪走,就开始自动切换
slider.start();
});
slider.addEventListener("slide", (evt) => { // 刚刚在类中重写的事件监听方法。
const idx = evt.detail.index; // 获取事件中数据载体detail中参数名为index的参数
const selected = controller.querySelector( // 从当前DOM元素取出当前选中的按钮
".slide-list__control-buttons--selected",
);
if (selected) selected.className = "slide-list__control-buttons"; // 将其设置成未选中
buttons[idx].className = "slide-list__control-buttons--selected"; // 将idx对应的按钮设置成选中
});
}
}
function pluginPrevious(slider) { // 切换前一张图片
const previous = slider.container.querySelector(".slide-list__previous");
if (previous) {
previous.addEventListener("click", (evt) => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
}
function pluginNext(slider) { // 切换后一张图片
const next = slider.container.querySelector(".slide-list__next");
if (next) {
next.addEventListener("click", (evt) => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
}
// 写了三个插件,然后进行注册。
const slider = new Slider("my-slider");
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();