注:内容来自奇舞学院前端星课程
组件的设计分三个步骤,如下:
接下来以轮播图组件的设计过程具体讲解组件设计过程。
,图片放
HTML代码
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
li>
<li class="slider-list__item">
<img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
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--selected{
position: absolute; /* 绝对定位使列表项目重叠在一起 */
transition: opacity 1s;
opacity: 0; /* 默认透明隐藏元素 */
text-align: center;
}
.slider-list__item--selected{
transition: opacity 1s;
opacity: 1; /* 选中时显示元素 */
}
class
,用面向对象的方式实现。 class Slider {
constructor(id) { // 构造函数的参数id为要轮播图组件的id,初始化container和items属性
// 获取轮播图的容器元素
this.container = document.getElementById(id)
// 获取轮播图中的图片列表
this.items = this.container.querySelectorAll('.slider-list__item,.slider-list__item--selected')
}
/* Slider类的方法 */
getSelectedItem () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
return selectedItem
}
getSelectedItemIndex () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
// this.items的类型是类数组对象,需要转换为数组类型
return Array.from(this.items).indexOf(selectedItem)
}
slideTo (index) {
const selectedItem = this.getSelectedItem()
if(selectedItem) selectedItem.className = 'slider-list__item'
const idx = index%this.items.length
if(this.items[idx]) this.items[idx].className = 'slider-list__item--selected'
}
slideNext () {
const currentId = this.getSelectedItemIndex()
this.slideTo(currentId + 1)
}
slidePrevious () {
const currentId = this.getSelectedItemIndex()
this.slideTo(this.tems.length + currentId - 1)
}
}
// 实例化对象
const slider = new Slider('my-slider')
// 自动轮播
setInterval(() => {
slider.slideNext()
}, 3000);
首先要添加控制结构,即在html和css中添加轮播图的上下切换按钮和控制条。然后在组件构造函数中给控制结构添加相应事件处理。
添加控制流版本的轮播图代码如下:
代码中的修改:html中加入按钮和控制条并给相应的样式,Slider类构造函数中给控制条添加悬浮事件切换选项和上下切换按钮添加点击事件,Slider类加了两个函数
start
和stop
来控制是否自动轮播效果。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>轮播图title>
<style>
#my-slider{
position: relative;
width: 790px;
height: 340px;
}
.slider-list ul{
list-style-type:none;
position: relative;
padding: 0;
margin: 0;
}
.slider-list__item,
.slider-list__item--selected{
position: absolute; /* 绝对定位使列表项目重叠在一起 */
transition: opacity 1s;
opacity: 0; /* 默认透明隐藏元素 */
text-align: center;
}
.slider-list__item--selected{
transition: opacity 1s;
opacity: 1; /* 选中时显示元素 */
}
/* 上下切换按钮 */
.slide-list__next,
.slide-list__previous{
display: inline-block;
position: absolute;
top: 50%;
margin-top: -25px;
width: 30px;
height:50px;
text-align: center;
font-size: 24px;
line-height: 50px;
overflow: hidden;
border: none;
background: transparent;
color: white;
background: rgba(0,0,0,0.2);
cursor: pointer;
opacity: 0;
transition: opacity .5s;
}
.slide-list__previous {
left: 0;
}
.slide-list__next {
right: 0;
}
#my-slider:hover .slide-list__previous {
opacity: 1;
}
#my-slider:hover .slide-list__next {
opacity: 1;
}
.slide-list__previous:after {
content: '<';
}
.slide-list__next:after {
content: '>';
}
/* 控制条样式 */
.slide-list__control{
position: relative;
display: table;
background-color: rgba(255, 255, 255, 0.5);
padding: 5px;
border-radius: 12px;
top: 280px;
margin: auto;
}
.slide-list__control-buttons,
.slide-list__control-buttons--selected{
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
margin: 0 5px;
background-color: white;
cursor: pointer;
}
.slide-list__control-buttons--selected {
background-color: red;
}
style>
head>
<body>
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
li>
<li class="slider-list__item">
<img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
li>
ul>
<a class="slide-list__next">a>
<a class="slide-list__previous">a>
<div class="slide-list__control">
<span class="slide-list__control-buttons--selected">span>
<span class="slide-list__control-buttons">span>
<span class="slide-list__control-buttons">span>
<span class="slide-list__control-buttons">span>
div>
div>
<script>
class Slider {
constructor(id, cycle = 3000) { // 构造函数的参数id为要轮播图组件的id,cycle为轮播周期
this.cycle = cycle
// 获取轮播图的容器元素
this.container = document.getElementById(id)
// 获取轮播图中的图片列表
this.items = this.container.querySelectorAll('.slider-list__item,.slider-list__item--selected')
// 添加控制流
const controller = this.container.querySelector('.slide-list__control')
// 控制轮播图选中控制条显示对应图
if (controller) {
const buttons = controller.querySelectorAll('.slide-list__control-buttons,.slide-list__control-buttons--selected')
controller.addEventListener('mouseover', event => {
const idx = Array.from(buttons).indexOf(event.target)
if (idx >= 0) {
this.slideTo(idx)
this.stop()
}
})
controller.addEventListener('mouseout', event => {
this.start()
})
// 滑动时修改控制条选中样式
this.container.addEventListener('slide', event => {
const idx = event.detail.index
console.log('索引', idx, buttons)
const selectedItem = controller.querySelector('.slide-list__control-buttons--selected')
if(selectedItem) selectedItem.className = 'slide-list__control-buttons'
if(buttons[idx]) buttons[idx].className = 'slide-list__control-buttons--selected'
})
}
// 为上下切换按钮添加点击事件
const previous = this.container.querySelector('.slide-list__previous')
if(previous){
previous.addEventListener('click', evt => {
this.stop()
this.slidePrevious()
this.start()
evt.preventDefault()
});
}
const next = this.container.querySelector('.slide-list__next')
if(next){
next.addEventListener('click', evt => {
this.stop()
this.slideNext()
this.start()
evt.preventDefault()
});
}
}
/* Slider类的方法 */
getSelectedItem () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
return selectedItem
}
getSelectedItemIndex () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
// this.items的类型是类数组对象,需要转换为数组类型
return Array.from(this.items).indexOf(selectedItem)
}
slideTo (index) {
const selectedItem = this.getSelectedItem()
if(selectedItem) selectedItem.className = 'slider-list__item'
const idx = index%this.items.length
if(this.items[idx]) this.items[idx].className = 'slider-list__item--selected'
// 触发滑动事件
const event = new CustomEvent('slide',
{
bubbles:true,
detail: {index: idx}
}
)
this.container.dispatchEvent(event)
}
slideNext () {
const currentId = this.getSelectedItemIndex()
this.slideTo(currentId + 1)
}
slidePrevious () {
const currentId = this.getSelectedItemIndex()
this.slideTo(this.items.length + currentId - 1)
}
start () {
this.stop()
this._timer = setInterval(() => {
this.slideNext()
}, this.cycle);
}
stop () {
if(this._timer) clearInterval(this._timer)
}
}
// 实例化对象
const slider = new Slider('my-slider')
// 自动轮播
slider.start()
script>
body>
html>
这个版本的轮播图已经实现了该有的功能,但是代码还需要进行优化,将组件中的一些东西抽象出来。在Slider组件的切换按钮和控制条都可以抽象出来作为插件,然后插件将Slider组件对象作为依赖注入,降低控制流插件与组件的耦合度。
js代码中在组件的构造函数中添加的控制流抽离出来作为注册插件函数,组件对象作为参数传入注册插件函数。
修改后的js代码如下:
class Slider {
constructor(id, cycle = 3000) { // 构造函数的参数id为要轮播图组件的id,cycle为轮播周期
this.cycle = cycle
// 获取轮播图的容器元素
this.container = document.getElementById(id)
// 获取轮播图中的图片列表
this.items = this.container.querySelectorAll('.slider-list__item,.slider-list__item--selected')
}
/* Slider类的方法 */
registerPlugins (...plugins) { // 引入插件函数
plugins.forEach(plugin => plugin(this))
}
getSelectedItem () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
return selectedItem
}
getSelectedItemIndex () {
const selectedItem = this.container.querySelector('.slider-list__item--selected')
// this.items的类型是类数组对象,需要转换为数组类型
return Array.from(this.items).indexOf(selectedItem)
}
slideTo (index) {
const selectedItem = this.getSelectedItem()
if(selectedItem) selectedItem.className = 'slider-list__item'
const idx = index%this.items.length
if(this.items[idx]) this.items[idx].className = 'slider-list__item--selected'
// 触发滑动事件
const event = new CustomEvent('slide',
{
bubbles:true,
detail: {index: idx}
}
)
this.container.dispatchEvent(event)
}
slideNext () {
const currentId = this.getSelectedItemIndex()
this.slideTo(currentId + 1)
}
slidePrevious () {
const currentId = this.getSelectedItemIndex()
this.slideTo(this.items.length + currentId - 1)
}
start () {
this.stop()
this._timer = setInterval(() => {
this.slideNext()
}, this.cycle);
}
stop () {
if(this._timer) clearInterval(this._timer)
}
}
/* 抽象出来的插件函数 */
// 控制条
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', event => {
const idx = Array.from(buttons).indexOf(event.target)
if (idx >= 0) {
slider.slideTo(idx)
slider.stop()
}
})
controller.addEventListener('mouseout', event => {
slider.start()
})
// 滑动时修改控制条选中样式
slider.container.addEventListener('slide', event => {
const idx = event.detail.index
console.log('索引', idx, buttons)
const selectedItem = controller.querySelector('.slide-list__control-buttons--selected')
if(selectedItem) selectedItem.className = 'slide-list__control-buttons'
if(buttons[idx]) 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();
})
}
}
// 实例化对象
const slider = new Slider('my-slider')
// 自动轮播
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start()
经过js的修改将控制流作为插件引入,但是这样插件的是否引入对html结构并没有影响。所以这里还需要改进插件,将组件和插件模板化。这里需要再设计了组件和插件API(如下图),在插件中加入了render
和action
方法:render
方法根据传入render
方法的数据data
来构造html结构;action
方法根据传入的组件对象component
作为依赖注入,从而给组件添加事件和行为。
具体实现:
<div id="my-slider" class="slider-list">div>
class Slider {
constructor(id, options={images: [], cycle: 3000}) {
// 构造函数的参数id为要轮播图组件的id,options为参数
// 获取轮播图的容器元素
this.container = document.getElementById(id)
// 获取渲染组件的参数
this.options = options
this.container.innerHTML = this.render()
// 获取轮播图中的图片列表
this.items = this.container.querySelectorAll('.slider-list__item,.slider-list__item--selected')
this.cycle = options.cycle || 3000
this.slideTo(0)
}
/* Slider类的函数 */
// 渲染html结构
render () {
const images = this.options.images
const content = images.map(item => `
${item} "/>
`.trim())
return `
${content.join('')}`
}
// 引入插件函数
registerPlugins (...plugins) {
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div')
pluginContainer.className = '.slider-list__plugin'
// 渲染插件html结构
pluginContainer.innerHTML = plugin.render(this.options.images)
this.container.appendChild(pluginContainer)
// 添加事件和行为
plugin.action(this)
})
}
/*
......
此处省略了未改动的函数
*/
}
/* 抽象出来的插件函数 */
// 控制条
const pluginController = {
render (data) {
return `
`.trim()
},
action (component) {
// 添加控制流
const controller = component.container.querySelector('.slide-list__control')
// 控制轮播图选中控制条显示对应图
if (controller) {
const buttons = controller.querySelectorAll('.slide-list__control-buttons,.slide-list__control-buttons--selected')
controller.addEventListener('mouseover', event => {
const idx = Array.from(buttons).indexOf(event.target)
if (idx >= 0) {
component.slideTo(idx)
component.stop()
}
})
controller.addEventListener('mouseout', event => {
component.start()
})
// 滑动时修改控制条选中样式
component.container.addEventListener('slide', event => {
const idx = event.detail.index
console.log('索引', idx, buttons)
const selectedItem = controller.querySelector('.slide-list__control-buttons--selected')
if(selectedItem) selectedItem.className = 'slide-list__control-buttons'
if(buttons[idx]) buttons[idx].className = 'slide-list__control-buttons--selected'
})
}
}
}
// 上一页按钮
const pluginPrevious = {
render (data) {
return `
`.trim()
},
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 (data) {
return `
`.trim()
},
action (slider) {
const next = slider.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
})
}
}
}
const 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'
]
// 实例化对象
const slider = new Slider('my-slider', {images})
// 自动轮播
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start()
现在将插件模板化后,当不引用某个插件时界面上也不显示了。现在通过组件模型抽象来进一步改进,其类图结构如下。
具体实现:
class Component {
constructor(id, options = {data:[]}) {
this.container = document.getElementById(id)
this.options = options
this.container.innerHTML = this.render(options.data)
}
registerPlugins(...plugins) {
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div')
pluginContainer.className = '.slider-list__plugin'
// 渲染插件html结构
pluginContainer.innerHTML = plugin.render(this.options.data)
this.container.appendChild(pluginContainer)
// 添加事件和行为
plugin.action(this)
})
}
render(data) {
/* 抽象函数 */
return ''
}
}
class Slider extends Component {
constructor(id, options={name: 'slider-list',data: [], cycle: 3000}) {
super(id,options)
// 获取轮播图中的图片列表
this.items = this.container.querySelectorAll('.slider-list__item,.slider-list__item--selected')
this.cycle = options.cycle || 3000
this.slideTo(0)
}
/* Slider类的方法 */
// 渲染html结构
render (data) {
const content = data.map(item => `
${item} "/>
`.trim())
return `
${content.join('')}`
}
/*
......
此处省略了未改动的函数
*/
}
/* 抽象出来的插件函数 */
// 控制条
const pluginController = {
render (data) {
return `
`.trim()
},
action (component) {
// 添加控制流
const controller = component.container.querySelector('.slide-list__control')
// 控制轮播图选中控制条显示对应图
if (controller) {
const buttons = controller.querySelectorAll('.slide-list__control-buttons,.slide-list__control-buttons--selected')
controller.addEventListener('mouseover', event => {
const idx = Array.from(buttons).indexOf(event.target)
if (idx >= 0) {
component.slideTo(idx)
component.stop()
}
})
controller.addEventListener('mouseout', event => {
component.start()
})
// 滑动时修改控制条选中样式
component.container.addEventListener('slide', event => {
const idx = event.detail.index
console.log('索引', idx, buttons)
const selectedItem = controller.querySelector('.slide-list__control-buttons--selected')
if(selectedItem) selectedItem.className = 'slide-list__control-buttons'
if(buttons[idx]) buttons[idx].className = 'slide-list__control-buttons--selected'
})
}
}
}
// 上一页按钮
const pluginPrevious = {
render (data) {
return `
`.trim()
},
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 (data) {
return `
`.trim()
},
action (slider) {
const next = slider.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
})
}
}
}
const 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'
]
// 实例化对象
const slider = new Slider('my-slider', {data:images})
// 自动轮播
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start()
</script>
通过一步步地拆解和抽象出来,减少他们之间的依赖关系,无论是组件还是插件都能独立出来。当我们的界面交互发生一小部分变化时我们只需要去修改所涉及到的插件就行,而不需要去修改整个组件代码结构,增加代码的可维护性。