JSFiddle - React, Tailwind, and code Playground

by HDL52

HTML

<div class="wrap">
  <ul class="list">
    <!--这里是图片列表-->
    <li class="item"><img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgied0v0dgsgyqeoh7804b0ca54buj0qd.jpg" /></li>
    <li class="item"><img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imge39twvhffwemdsipqncbwko5vayrxgd.jpg" /></li>
    <li class="item"><img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/img1u6qw3ad64bz8nsedrd2wddgrax5wew.jpg" /></li>
    <li class="item"><img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgodr8vukcwco0h34p0ngc84462vs454j.jpg" /></li>
  </ul>
  <ul class="dot">
    <!--这里是下面的小点-->
    <li class="active"></li>
    <li></li>
    <li></li>
    <li></li>
  </ul>
</div>

CSS

body {
  margin: 0;
  background: #fefefe;
}

.wrap {
  width: 100vw; /* 最好是固定宽 */
  overflow: hidden;
  box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1);
  position: relative;
  background: #000;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.list {
  display: flex;
  float: left;
}

.list img {
  width: 100vw;
  vertical-align: top;
  filter: saturate(2);
}

.dot {
  width: 100vw;
  display: flex;
  justify-content: center;
  position: absolute;
  bottom: 20px;
}

.dot li {
  width: 10px;
  height: 10px;
  border-radius: 10px;
  background: #fff;
  margin: 0 5px;
  transition: 0.2s;
  box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.3);
}

.dot li.active {
  width: 20px;
}

JavaScript

let wrap = document.querySelector(".wrap") // 获取外框
let list = wrap.querySelector(".list") // 图片列表
let dots = wrap.querySelectorAll(".dot li") // 小点

let startpoint = {} // 记录起点位置
let distance = {} // 记录移动距离
let startOffset = 0 // 开始时图片列表的位置
let translateX = 0 // 当前移动距离
let now = 0 // 当前图片索引
let imgW = wrap.offsetWidth // 外框宽度
let proportion = 0.3 // 切换图片的比例
let isMove = false // 是否在移动
let interval // 自动轮播的定时器

// 复制图片列表,实现无缝滚动
list.innerHTML += list.innerHTML

// 设置列表宽度,容纳两倍的图片
list.style.width = `${list.children.length * 100}vw`

// 初始化自动轮播
startAutoSlide()

// 监听 pointerdown 事件
wrap.addEventListener("pointerdown", (ev) => {
  ev.preventDefault() // 阻止默认行为,避免页面滚动

  clearInterval(interval) // 暂停自动轮播

  startpoint = { x: ev.clientX, y: ev.clientY } // 记录起始点
  isMove = true // 标记为移动状态

  // 取消过渡动画
  list.style.transition = "none"
  startOffset = now * -imgW
})

// 监听 pointermove 事件
wrap.addEventListener("pointermove", (ev) => {
  if (!isMove) return // 未在移动时不执行

  distance = { x: ev.clientX - startpoint.x, y: ev.clientY - startpoint.y }

  // 判断横向移动是否大于纵向移动,避免页面跟随滑动
  if (Math.abs(distance.x) > Math.abs(distance.y)) {
    translateX = startOffset + distance.x
    list.style.transform = `translateX(${translateX}px)`
  }
})

// 监听 pointerup 事件
wrap.addEventListener("pointerup", () => {
  if (!isMove) return // 未触发移动时不执行

  // 根据滑动距离判断是否切换图片
  if (Math.abs(distance.x) > imgW * proportion) {
    now -= Math.sign(distance.x) // 根据滑动方向更新索引
  }

  // 限制索引范围
  now = Math.max(0, Math.min(now, list.children.length / 2 - 1))

  updateSlide() // 更新图片位置
  isMove = false // 重置移动状态

  // 重新启动自动轮播
  startAutoSlide()
})

// 更新图片位置及小点状态
function updateSlide() {
  list.style.transition = "transform 0.3s ease" // 恢复动画
  list.style.transform = `translateX(${-now * imgW}px)` // 移动到目标位置

  // 更新小点的状态
  dots.forEach((dot, index) => {
    dot.classList.toggle("active", index === now % dots.length)
  })

  // 边界处理,实现无缝滚动
  if (now === 0 || now === dots.length) {
    setTimeout(() => {
    ...