本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
一个比较常见的需求,给定一个图片列表,在一行或者一列上展示这些图片的缩略图,如果一行或者一列上放不下,则通过翻页或滚动的方式,展示全部的缩略图,然后,被点击的缩略图,以大图的形式展示出来。
个人遇到过几次这种需求,于是决定用原生手写一个组件,方便以后使用,效果如下所示。
效果
基本思路
用原生JS创建组件,一般分为三个部分,渲染DOM元素、挂载监听事件、逻辑处理。本文也是如此,按照这三个步骤逐步编写相关的代码。
而组件本身,可以按照功能分成以下几个区域:
事先准备
用原生JS写的组件,一般在使用时,都需要指定渲染的容器,然后通过JS在指定区域进行渲染即可,所以先创建一个容器,方便后续编写代码进行测试:
<div id="app">
<div id="container"></div>
</div>
注意:容器必须先设定其宽高,以下是样式,比较常规,不再赘述:
#container {
width: 300px;
height: 320px;
position: absolute;
background: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
编写组件
基本代码
首先,定义一个类,使用其来创建组件,先把基本代码写好:
class ImgShowSelector {
constructor() {}
}
接下来,便是确定该组件所需的参数,本文编写的组件接受四个参数:组件容器container、图片列表imgList、缩略图显示的图片数量thumbnailShowNum(一次能显示多少缩略图)、缩略图的高度,添加后代码如下:
class ImgShowSelector {
// 容器元素,调用时传入
container = null
// 容器元素的信息
containerInfo = {}
// 缩略图显示的图片数量
thumbnailShowNum = 4
/**
* 构造器
* @param { HTMLElement } container 容器元素
* @param { Array<string> } imgList 图片列表
* @param { Number } thumbnailShowNum 缩略图显示的图片数量
* @param { Number } thumbnailHeight 缩略图高度,单位px
* */
constructor(container, imgList, thumbnailShowNum = 4, thumbnailHeight = 75) {
// 判断container是否为HTMLElement
if (!container || container.nodeType !== 1) {
throw new Error('container is not HTMLElement !')
}
// 判断图片列表是否为空
if (!imgList || imgList.length === 0) {
throw new Error('The picture list does not exist or the length is zero, so it will not be created !')
}
// 记录数据
this.container = container
this.imgList = imgList
this.thumbnailShowNum = thumbnailShowNum
}
}
渲染元素
之后就是渲染组件的DOM元素了,由于渲染的内容比较多,就不全部展示出来,以展示大图展示区域为例,如下:
要展示图片,就得有相应的元素才行,可以通过const div = document.createElement('div')创建相应的元素,然后添加相应的样式,因为不想用class污染全局,所以本文全部写成行内样式;再把创建出来的元素添加组件容器container或者相应的父级容器中即可,当然,有一些标签在创建时有特殊的逻辑需要处理,比如,设置src之类的。展示大图展示区域的渲染代码如下:
/** 渲染器 大图展示 */
_renderBigImg () {
// 创建元素
const div = document.createElement('div')
// 添加样式
div.style = `background: linear-gradient(
45deg,
rgba(0, 0, 0, 0.4) 25%,
transparent 25%,
transparent 75%,
rgba(0, 0, 0, 0.4) 75%,
rgba(0, 0, 0, 0.4) 100%
),
linear-gradient(
45deg,
rgba(0, 0, 0, 0.4) 25%,
transparent 25%,
transparent 75%,
rgba(0, 0, 0, 0.4) 75%,
rgba(0, 0, 0, 0.4) 100%
);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
flex: 1;`
// 放入容器
this.container.append(div)
// 创建图片标签
this.bigImgDOM = document.createElement('img')
// 设定地址
this.bigImgDOM.src = this.imgList[this.bigImgIndex]
// 添加样式
this.bigImgDOM.style = 'width: 100%;height:100%;object-fit:contain;'
// 添加到父级元素中
div.append(this.bigImgDOM)
}
其他元素也是同样的处理方式,不再一一赘述。
挂载监听事件
对于按钮翻页处理,基本逻辑是先记录当前展示的第一张缩略图的索引,用这个索引加上或者减去缩略图显示的图片数量thumbnailShowNum,得到一个新的索引,判断这个索引是否在合理的范围内,如果是,缩略图展示区域通过tranform: translateX()平移相应的距离,达到翻页的效果。
至于这个距离的计算,需要用到缩略图的宽度(px值) * 新的索引 * -1,因为缩略图的平移的最大值是0,如果超过0,则会将整个缩略图平移的到可视区域的右侧,这显然是不合理的。向前翻页跟向后翻页的处理逻辑上大同小异,此处展示向前翻页的主要代码:
// 向前翻页按钮添加监听事件
this.preBtn.addEventListener('mouseup', (event) => {
// 判断缩略图的宽度是否已经计算出来,没有则进行计算
this.thumbnailWidthpx === 0 && this._computedThumbnailWidthpx()
// 设置鼠标抬起后,按钮的样式
this.preBtn.style.boxShadow = '5px 5px 10px rgba(121, 130, 160, 0.55)'
// 计算新的索引,因为是向前翻页,所以用当前的展示索引减去缩略图的展示数量
const newIndex = this.selectorImgIndex - this.thumbnailShowNum
// 新的索引符合要求,则缩略图展示区域平移相应的距离,同时记录新的展示索引
newIndex >= 0 && (this.selectorDOM.style.transform = `translateX(-${this.thumbnailWidthpx * newIndex}px)`, this.selectorImgIndex = newIndex)
})
然后是缩略图的点击事件,点击缩略图后,展示相应的大图;这个比较简单,可以在每个缩略图标签上添加dataset.index属性,用于指定的图片地址在图片列表中的索引,通过事件委托,收集缩略图的点击事件,统一处理,主要代码如下:
// 在缩略图的父级元素上监听点击事件,进行事件委托
this.selectorDOM.addEventListener('click', (event) => {
// 判断是否存在图片索引的dataset,如果没有,不进行任何操作
if (!event.target || !event.target.dataset || !event.target.dataset.index || !this.thumbnailDOMList[+event.target.dataset.index]) {
return false
}
// 取消原本被选中的缩略图的选中样式
const thumbnail = this.thumbnailDOMList[this.bigImgIndex]
thumbnail.style.background = '#fff'
// 更改展示的大图的图片索引
this.bigImgIndex = +event.target.dataset.index
// 更改大图的图片地址和给新选中的缩略图添加样式
this._changeBigImage()
})
至此,基本处理完毕。
总结
第一次使用原生js编写组件,总的来说不是很难,但是比较内容比较多,很多地方顾此失彼。而且本文记叙的组件功能也是比较简单的,诸如跳转到指定的缩略图、添加图片、删除指定图片等等功能还未加上,有待扩展。