likes
comments
collection
share

给轮播图做一个自适应的高度。

作者站长头像
站长
· 阅读数 15

不知道大家有没有遇到这样的需求或者说看到类似的效果,就是列表进去详情看轮播图的时候,当手指滚动轮播图时轮播的高度容器会自适应,这样下面的内容就向上挤,滑动的过程会计算高度,释放的时候也会滚到下一张,也会计算对应图片的高度,然后做一个缓动的动画效果。就像下面这张图的样子。

给轮播图做一个自适应的高度。

可以看到上面的图片内容文字,随着轮播的滑动高度也在变化。费话不多说直接上代码。

实现方法

可以通过监听鼠标mounse 或者手指的滑动 touch 事件来控制图片,这里本文只说一下轮播的功能实现思路,重点说的是怎么实现高度的自适应。

直接开始正文,先看 html 代码结构。

html 结构

<div class="container">
  <div class="wrapper">
    <div class="swiper">
      <div class="item">
        <img src="https://ci.xiaohongshu.com/776d1cc7-ff36-5881-ad8f-12a5cd1c3ab3?imageView2/2/w/1080/format/jpg" alt="">
      </div>
      <div class="item">
        <img src="https://ci.xiaohongshu.com/b8e16620-66a0-79a5-8a4b-5bfee1028554?imageView2/2/w/1080/format/jpg" alt="">
      </div>
      <div class="item">
        <img src="https://ci.xiaohongshu.com/e12013c2-3c46-a2cc-7fda-1e0b20b36f3d?imageView2/2/w/1080/format/jpg" alt="">
      </div>
    </div>
  </div>
  <div class="content">这是一段内容</div>
</div>

css 样式

.container {
  width: 100%;
  overflow: hidden;
}
.wrapper {
  width: 100%;
}
.swiper {
  font-size: 0;
  white-space: nowrap;
}
.item {
  display: inline-block;
  width: 100%;
  vertical-align: top; // 一定要使用顶部对齐,不然会出现错位的情况
}
.item img {
  width: 100%;
  height: auto;
  display: block;
}
.content {
  position: relative;
  z-index: 9;
  font-size: 14px;
  text-align: center;
  padding-top: 20px;
  background-color: #fff;
  height: 200px;
}

值得注意的地方有几点;

  1. 在使用父级 white-space 时,子集元素设置 display: inline-block 会出现高度不同的排列错位,解决办法就是加上一句 vertical-align: top ,具体什么原因我也不细讲了。
  2. 另外父级还要设置 font-size: 0 ,如果没加上的话,就会出现两个子集有空隙出现,加上之后空隙就会去掉。
  3. img 图片最好设置成高度自适应,宽度100% 还要加上 display: block ,没有的话底部就会出现间隙。

写好上面的 html容器部分和 样式,下面就看一下 js 上是怎么处理的。

Js 实现

开始之前我们先思考一下去怎么实现这个轮播以及高度的自适应问题,分为几步操作;

  1. 鼠标按下时,需要记录当前的位置和一些其他初始化的信息,并且给当前的父元素添加相应的鼠标事件。
  2. 鼠标移动时,需要通过当前实时移动时点位和按下时点位的相减,得到移动的距离位置,然后再赋值给父元素设置其样式 transform 位置,中间还做其他的边界处理,当然还有高度的变化。
  3. 鼠标释放是,通过移动时记录的距离信息判断是左滑还是右滑,拿到其对应的索引,通过索引就可以计算到滚动下一张的距离,释放之后设置 transition 过渡动画即可。

按照我们试想的思路,开始正文;

初始化数据

const data = {
  ele: null,
  width: 0,
  len: 0,
  proportion: .3,
  type: false,
  heights: [500, 250, 375],
  currentIndex: 0,
  startOffset: 0,
  clientX: 0,
  distanceX: 0,
  duration: 30,
  touching: false
}
​
const wrapper = data.ele = document.querySelector('.wrapper')
const items = document.querySelectorAll('.item')
data.width = wrapper.offsetWidth
data.len = items.length - 1
wrapper.addEventListener('touchstart', onStart)
wrapper.addEventListener('mousedown', onStart)

注意,这里在做高度之前,我们需要等图片加载完成之后才能拿到每一个元素的高度,我这里为了省懒就没写具体代码,上面的 heights 对应的是每个图片在渲染之后的高度,一般情况下最好让后端传回来带宽高,这样就不需要用 onload 再去处理这个。

鼠标按下时

function onStart(event) {
  if (event.type === 'mousedown' && event.which !== 1) return
  if (event.type === 'touchstart' && event.touches.length > 1) return
  data.type = event.type === 'touchstart'
  const events = data.type ? event.touches[0] || event : event
​
  data.touching = true
  data.clientX = events.clientX
  data.startOffset = data.currentIndex * -data.width
​
  data.ele.style.transition = `none`
  window.addEventListener(data.type ? 'touchmove' : 'mousemove', onMove, { passive: false })
  window.addEventListener(data.type ? 'touchend' : 'mouseup', onEnd, false)
}

上面的代码里面我做了PC和移动端的兼容,跟计划的一样,保存一下 clientX 坐标和一个初始的坐标 startOffset 这个由当前索引和父级宽度计算得到,场景是当从第二张图片滚动到第三张图片时,会把之前的第一张图片的距离也要加上去,不然就计算错误,看下面滑动时的代码。

另外在做监听移动的时候加上了 passive: false 是为了在移动端兼容处理。

鼠标移动时

function onMove(event) {
  event.preventDefault()
  if (!data.touching) return
  const events = data.type ? event.touches[0] || event : event
​
  data.distanceX = events.clientX - data.clientX
​
  let translatex = data.startOffset + data.distanceX
  if (translatex > 0) {
    translatex = translatex > 30 ? 30 : translatex
  } else {
    const d = -(data.len * data.width + 30)
    translatex = translatex < d ? d : translatex
  }
​
  data.ele.style.transform = `translate3d(${translatex}px, 0, 0)`
  data.ele.style.webkitTransform = `translate3d(${translatex}px, 0, 0)`
}

做了一个边界处理的,超了 30 的距离就不让继续滑动了,加上之前保存的 startOffset 的值,得到的就是具体移动的距离了。

鼠标释放时

function onEnd() {
  if (!data.touching) return
  data.touching = false
​
  // 通过计算 proportion 滑动的阈值拿到释放后的索引
  if (Math.abs(data.distanceX) > data.width * data.proportion) {
    data.currentIndex -= data.distanceX / Math.abs(data.distanceX)
  }
  if (data.currentIndex < 0) {
    data.currentIndex = 0
  } else if (data.currentIndex > data.len) {
    data.currentIndex = data.len
  }
  const translatex = data.currentIndex * -data.width
​
  data.ele.style.transition = 'all .3s ease'
  data.ele.style.transform = `translate3d(${translatex}px, 0, 0)`
  data.ele.style.webkitTransform = `translate3d(${translatex}px, 0, 0)`
​
  window.removeEventListener(data.type ? 'touchmove' : 'mousemove', onMove, { passive: false })
  window.removeEventListener(data.type ? 'touchend' : 'mouseup', onEnd, false)
}

通过计算 proportion 滑动的阈值拿到释放后的索引,也就是超过父级宽度的三分之一时释放就会滚动到下一张,拿到索引之后就可以设置需要移动的最终距离,记得加上 transition 做一个缓动效果,最后也别忘记移除事件的监听。

至此上面的简单的轮播效果就大功告成了,但是还缺少一点东西,就是本篇需要讲的自适应高度,为了方便理解就单独拿出来说一下。

高度自适应

在移动时就可以在里面做相关的代码整理了, onMove 函数里加上以下代码,来获取实时的高度。

const index = data.currentIndex
const currentHeight = data.heights[index]
    
// 判断手指滑动的方向拿到下一张图片的高度
let nextHeight = data.distanceX > 0 ? data.heights[index - 1] : data.heights[index + 1]
let diffHeight = Math.abs((nextHeight - currentHeight) * (data.distanceX / data.width))
let realHeight = currentHeight + (nextHeight - currentHeight > 0 ? diffHeight : -diffHeight)
 
 data.ele.style.height = `${realHeight}px`

这里是移动时的高度变化,另外还需要在释放时也要处理, onEnd 函数里加上以下代码。

// ... 因为上面已经拿到了下一张的索引 currentIndex
const currentHeight = data.heights[data.currentIndex]
​
data.ele.style.height = `${currentHeight}px`

因为上面已经拿到了下一张的索引 currentIndex 所以再滚动到下一张是就直接通过数据获取就可以了。

可以在线预览一下效果。