原生JS-简单写个选择图片展示组件

2,021 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

一个比较常见的需求,给定一个图片列表,在一行或者一列上展示这些图片的缩略图,如果一行或者一列上放不下,则通过翻页或滚动的方式,展示全部的缩略图,然后,被点击的缩略图,以大图的形式展示出来。
个人遇到过几次这种需求,于是决定用原生手写一个组件,方便以后使用,效果如下所示。

效果

基本思路

用原生JS创建组件,一般分为三个部分,渲染DOM元素、挂载监听事件、逻辑处理。本文也是如此,按照这三个步骤逐步编写相关的代码。
而组件本身,可以按照功能分成以下几个区域: 微信截图_20220613214654.png

事先准备

用原生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编写组件,总的来说不是很难,但是比较内容比较多,很多地方顾此失彼。而且本文记叙的组件功能也是比较简单的,诸如跳转到指定的缩略图、添加图片、删除指定图片等等功能还未加上,有待扩展。