组件封装:指web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。
本文以实现一个轮播图组件为例:
首先是html的部分,html使用无序列表实现
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list_item">
<img src="xxx">
</li>
<li class="slider-list_item">
<img src="xxx">
</li>
<li class="slider-list_item-seleted">
<img src="xxx">
</li>
<li class="slider-list_item">
<img src="xxx">
</li>
</ul>
</div>
CSS部分:
#my-slider{
position: relative;
width: 790px;
}
.slider-list ul{
list-style-type: none;
position: relative;
padding: 0;
margin: 0;
}
.slider-list_item,.slider-list_item-seleted{
position: absolute;
transition: opacity 1s;
opacity: 0;
text-align: center;
}
.slider-list_item-seleted{
transition: opacity 1s;
opacity: 0;
}
JS部分
class Slider {
constructor(id) {
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll(
".slider-list_item,.slider-list_item-selected"
);
}
getSelectedItem() {
const selected = this.container.querySelector(".slider-list_item-selected");
return selected;
}
getSelectedItemIndex() {
return Array.from(thiss.items).indexOf(this.getSelectedItem());
}
SlideTo(index) {
const selected = this.getSelectedItem();
if (selected) {
selected.className = "slider-list_item";
}
const item = this.items[index];
if (item) {
item.className = "slider-list_item-selected";
}
}
SlideNext() {
const index = this.getSelectedItemIndex();
const nextIndex = (index + 1) % this.items.length;
this.SlideTo(nextIndex);
}
SlidePrevious() {
const index = this.getSelectedItemIndex();
const preIndex = (this.items.length + index - 1) % this.items.length;
this.SlideTo(preIndex);
}
}
该部分定义了一个slider类,其中定义了许多轮播图的操作。
但是这种方式其实还是不够灵活,当轮播图中的内容要改变时,我们需要去修改很多代码,此时我们思考是否能够将组件插件化:将其中的控制元素抽取成插件,组件与插件之间通过依赖注入的方式建立联系。
function pluginController(slider){
const controller = slider.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.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 => {
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';
});
}
}
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();
});
}
}
组件封装总结:
- 结构设计
- 展现效果
- 行为设置
API(功能)
Event(控制流)
考虑到后续需要在该功能中加入底部小圆点标记的功能,因此将组件通用模型抽象出来
class Component{
constructor(id, opts = {name, data:[]}){
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render(opts.data);
}
registerPlugins(...plugins){
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div');
pluginContainer.className = `.${name}__plugin`;
pluginContainer.innerHTML = plugin.render(this.options.data);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
render(data) {
/* abstract */
return ''
}
}
其中,registPlugins就是用于注册插件的方法
在开发过程中,我们很容易就将js和html混在一起写,特别是功能点由少到多的时候,因此在开发之前,需要我们进行详细的设计,尽量考虑到多方因素,将js抽象出来,以便于后续代码的维护与修改。