源代码链接[code.juejin.cn/pen/7108191…]
- HTML初始结构
在HTML的<body>中只有一个空的<div id="my-slider" class="slider-list">元素。页面上的图片、按钮等内容完全通过JavaScript动态生成,不需要在HTML中事先编写结构代码。
-
Slider 类的基本结构与职责
Slider类负责管理整个幻灯片的基本结构和功能,包括将图片渲染到页面、图片的切换、自动轮播等。构造方法保持简洁,实例化时只需传入容器id和参数opts,然后通过render()方法生成图片的HTML并插入页面。这样HTML结构的生成交由Slider类处理,初始化过程更清晰。 -
插件的定义与作用
通过插件的设计,实现了代码的模块化和复用性。每个插件本质上就是将某些功能从Slider类中分离出来,插件都包含以下两个方法:render(): 用于生成该插件的HTML结构,并返回一段HTML字符串,这些内容将插入到页面上。action(): 定义插件的具体功能和交互逻辑(比如点击、鼠标悬停等事件的绑定),用于控制插件的行为。
-
Slider类的插件注册
插件注册方法registerPlugins()写在Slider类中,通过扩展运算符...一次性接收并注册多个插件。每次注册一个插件时,Slider类会创建一个<div>来包裹插件内容,将其插入到主容器中,同时调用action()为插件设置交互功能。插件的注册过程使用同一方法,形成统一的注册机制。 -
实现插件和主功能的分离
通过这种方式,核心功能与扩展功能完全分离——Slider类专注于幻灯片的核心逻辑,各种按钮、控制功能通过插件来实现。这样,如果需要新增或调整插件,只需添加或修改相应插件的代码,不影响Slider类的核心逻辑。最终,代码更加模块化、简洁,同时提升了代码的复用性和可维护性。
class Slider { // 定义一个Slider类
constructor(id, opts = { images: [], cycle: 3000 }) { // 定义一个构造函数,参数为id和opts。在opts里有两个变量,images,cycle。
this.container = document.getElementById(id); // 根据id获取DOM元素
this.options = opts; // 获取opts参数,用options接收
this.container.innerHTML = this.render(); // 将render()函数渲染到HTML上
this.items = this.container.querySelectorAll(
".slider-list__item, .slider-list__item--selected",
); // 把所有照片取出来
this.cycle = opts.cycle || 3000; // 取出参数opts里面的cycle变量,和3000进行“或”运算。如果不传的话默认值为3000。
this.slideTo(0); // 默认是第一张照片
}
render() { // 渲染方法。JSX格式
const images = this.options.images; // 从构造方法定义的options参数中,取出images变量
// images是所有的图片,进行map遍历,每一个图片定义为image,然后写成HTML形式。
// trim方法 去除前后空白,确保生成的 HTML 字符串不受多余空白影响。
const content = images.map((image) =>
`
<li class="slider-list__item">
<img src="${image}"/>
</li>
`.trim()
);
// .join("") 将 content 数组(包含各个 li 元素的字符串片段)合并成一个整体的字符串,并且不加任何分隔符。
return `<ul>${content.join("")}</ul>`; // 把定义的这些li元素插入到ul中
}
registerPlugins(...plugins) { // 注册插件,可以同时进行多个插件的注册,把要注册的插件写在一起就可以。
plugins.forEach((plugin) => {
const pluginContainer = document.createElement("div"); // 创建一个div元素,用来存放插件
pluginContainer.className = "slider-list__plugin"; // 设置div的类名
pluginContainer.innerHTML = plugin.render(this.options.images); // 插进进行渲染,插入到创建的div元素中
this.container.appendChild(pluginContainer); // 给最大的容器加入一个子元素。
plugin.action(this); // 调用插件的action函数
});
}
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";
}
let item = this.items[idx];
if (item) {
item.className = "slider-list__item--selected";
}
const detail = { index: idx };
const event = new CustomEvent("slide", { bubbles: true, detail });
this.container.dispatchEvent(event);
}
slideNext() { // 切换到下一张图片
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious() { // 切换到上一张图片
const currentIdx = this.getSelectedItemIndex();
const previousIdx = (this.items.length + currentIdx - 1) %
this.items.length;
this.slideTo(previousIdx);
}
addEventListener(type, handler) { // 事件监听函数。type为事件类型,handler为对应的事件
this.container.addEventListener(type, handler);
}
start() { // 开始自动播放
this.stop();
this._timer = setInterval(() => this.slideNext(), this.cycle);
}
stop() { // 停止自动播放 清空计时器
clearInterval(this._timer);
}
}
// 以下代码为定义插件
const pluginController = { // 定义一个按钮组插件
render(images) { // 定义一个渲染函数,渲染函数返回按钮组
return `
<div class="slide-list__control">
${
images.map((image, i) => `
<span class="slide-list__control-buttons${
i === 0 ? "--selected" : ""
}"></span>
`).join("")
}
</div>
`.trim();
},
action(slider) { // 定义一个action函数 转换 NodeList 为数组,便于查找事件目标的索引
const controller = slider.container.querySelector(".slide-list__control"); // 获取按钮组
if (controller) { // 如果获取到
const buttons = controller.querySelectorAll( // 拿到所有的按钮,这是一个NodeList对象
".slide-list__control-buttons, .slide-list__control-buttons--selected",
);
controller.addEventListener("mouseover", (evt) => { // 监听鼠标挪上来事件
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) => { // 监听自定义事件slide 如果自定义事件slide触发,那么就把idx对于图片的按钮设置成选中
const idx = evt.detail.index;
const selected = controller.querySelector(
".slide-list__control-buttons--selected",
);
if (selected) selected.className = "slide-list__control-buttons";
buttons[idx].className = "slide-list__control-buttons--selected";
});
}
},
};
const pluginPrevious = { // 定义切换前一张照片插件
render() {
return `<a class="slide-list__previous"></a>`;
},
action(slider) {
const previous = slider.container.querySelector(".slide-list__previous");
if (previous) {
previous.addEventListener("click", (evt) => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
},
};
const pluginNext = { // 定义切换后一张照片插件
render() {
return `<a class="slide-list__next"></a>`;
},
action(slider) {
const previous = slider.container.querySelector(".slide-list__next");
if (previous) {
previous.addEventListener("click", (evt) => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
},
};
const slider = new Slider("my-slider", {
images: [
"https://p5.ssl.qhimg.com/t0119c74624763dd070.png",
"https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg",
"https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg",
"https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg",
],
cycle: 3000,
});
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
现在我们加入一个新的插件,插件是一个按钮,点击按钮可以随机的显示一张照片。
-
定义插件,
const pluginButton -
render()函数 需要在页面中显示一个按钮,所以
return
'<button class="pluginButton1">随机显示一张照片</button>'有class的原因是我们需要进行点击事件的监听,需要通过class来找到需要监听事件的元素。
-
action()函数 需要一个参数slider,我们在插件中来对slider进行一系列操作
通过class找到我们要监听事件的元素.
const pluginButton1 = slider.container.querySeletor('.pluginButton1')现在我们取到了这个按钮元素,我们给这个按钮元素添加监听事件。
pluginButton1.addEventListener('click',()=>{})现在定义好监听事件的框架了,我们在{}里面写我们需要实现的功能。随机切换照片。
那么我们肯定就需要一个idx,切换到哪张照片。
通过
slider.items.length获取照片总数,然后* Math.random()可以得到一个有效的数,但是有可能不是整数,所以我们对结果再进行Math.floor()向下取整。然后我们停下当前的自动切换,slideTo(idx),切换到idx,然后再开始自动切换。
完整代码如下:
const pluginButton = {
render(){
return '<button class="pluginButton1">随机显示一张照片</button>'
},
action(slider){
const pluginButton1 = slider.container.querySelector(".pluginButton1");
pluginButton1.addEventListener('click',()=>{
slider.stop();
idx = Math.floor(slider.items.length * Math.random())
slider.slideTo(idx);
slider.start();
})
}
}
- 现在就只需要最后一步了,注册我们新创建的插件。
slider.registerPlugins(pluginController, pluginPrevious, pluginNext,pluginButton);
`
最后一种实现形式是在这一种的基础上,把Slider变得更为抽象,就不详细的介绍了,对应的代码在code.juejin.cn/pen/7108185…