发布于 2026-01-06 5 阅读
0

使用 Vue 和 GSAP 制作的动画卡片滑块 - WotW

使用 Vue 和 GSAP 制作的动画卡片滑块 - WotW

这是“每周小工具”系列的第三期

今天我将向大家展示如何使用 Vue 从零开始制作一个样式化的卡片滑块。

这个小部件的设计灵感来源于,外观如下:

滑块

准备工作

与上一个组件类似,今天的组件我们将使用vue.js进行交互,使用tweenlite进行动画。

HTML结构

滑块的基本元素包括卡片信息容器,我将首先添加它们以及一些类,以便在下一步中设置它们的样式:

<div id="slider" class="slider">
  <div class="slider-cards">
    <div class="slider-card"></div>
    <div class="slider-card"></div>
    <div class="slider-card"></div>
  </div>
  <div class="slider-info">
    <h1>Title</h1>
    <p>description</p>
    <button>Action</button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

造型!

目前它看起来离最终产品还差得很远。首先,我将使用以下规则模拟移动视口:

.slider {
  overflow: hidden;
  background-color: #1F1140;
  width: 360px;
  height: 640px;
}
Enter fullscreen mode Exit fullscreen mode

对于卡片,我会在容器内设置边距,使第一张卡片居中,然后卡片之间用右边距分隔。此外,我们需要将卡片容器设置为相对定位,并设置 z-index 值,使其位于slider-infodiv 元素之上。

卡片应该inline并排摆放,但要实现这一点,容器必须足够宽。本例中每张卡片的宽度约为 300 像素,因此容器的宽度为 900 像素,因为我们有 3 张卡片(如果有更多卡片,我们需要计算所需的总宽度)。

最后,我们将添加阴影效果,使卡片看起来像是悬浮在空中。

.slider-cards {
  position: relative;
  width: 900px;
  margin: 20px 50px;  
  z-index: 1;
}
.slider-card {
  display: inline-block;
  background-color: grey;
  overflow: hidden;
  width: 260px;
  height: 360px;
  margin-right: 30px;
  border-radius: 12px;
  box-shadow:0px 60px 20px -20px rgba(0, 0, 0, 0.3)
}
Enter fullscreen mode Exit fullscreen mode

我们越来越接近目标了。
半

现在轮到slider-info它进行改造了。我们将添加背景颜色、尺寸和边距,使信息居中显示。

重要的是它要与卡片容器重叠,为了做到这一点,它将margin-top是负数,为了补偿,我们添加一些padding-top

我们需要确保该overflow属性被隐藏,以使底部按钮与信息容器具有相同的圆角。之后,只需按以下方式设置标题、描述和按钮的样式即可:

.slider-info {
  position: relative;
  overflow: hidden;
  background-color: white;
  margin-top: -200px;
  margin-left: 30px;
  padding: 200px 20px 0;
  width: 260px;
  height: 200px;
  text-align: center;
  border-radius: 8px;
}
.slider-info h1 {
  font-family: Arial Black, Gadget, sans-serif;
  line-height: 25px;
  font-size: 23px;
}
.slider-info p {
  font-family: Arial, Helvetica, sans-serif;
}
.slider-button {
  position: absolute;
  width: 100%;
  height: 50px;
  bottom: 0;
  left: 0;
  border: none;
  color: white;
  background-color: #E71284;
  font-size: 18px;
  font-family: Arial, Helvetica, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

风格完成
好多了。

填充数据

我们准备开始使用 Vue,让我们创建一个实例并从电影数据库中设置一些数据:

new Vue({
  el: '#slider',
  data: {
    slides: [
      {
        title: 'Ready Player One',
        description: 'When the creator of a popular video game system dies, a virtual contest is created to compete for his fortune.',
        image: 'https://image.tmdb.org/t/p/w300_and_h450_bestv2/pU1ULUq8D3iRxl1fdX2lZIzdHuI.jpg'
      },
      {
        title: 'Avengers: Infinity War',
        description: 'As the Avengers and their allies have continued to protect the world from threats too large for any...',
        image: 'https://image.tmdb.org/t/p/w300_and_h450_bestv2/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg'
      },
      {
        title: 'Coco',
        description: 'Despite his family’s baffling generations-old ban on music, Miguel dreams of becoming an accomplished musician...',
        image: 'https://image.tmdb.org/t/p/w300_and_h450_bestv2/eKi8dIrr8voobbaGzDpe8w0PVbC.jpg'
      }
    ]
  }
})
Enter fullscreen mode Exit fullscreen mode

为了显示数据,我们需要定义默认选中的电影。这可以通过数据中的另一个变量selectedIndex和一个计算属性来实现,该属性可以根据选中的索引提供幻灯片中的数据:

  data: {
    // ... slide data
    selectedIndex: 0
  },
  computed: {
    selectedSlide () {
      return this.slides[this.selectedIndex]
    }
  }
Enter fullscreen mode Exit fullscreen mode

然后,在我们的模板中,我们将使用 <a> 标签将卡片绑定到 <input> 标签v-for,并将信息绑定到相应的数据:

<div id="slider" class="slider">
  <div class="slider-cards">
    <div 
         v-for="(slide, index) in slides" 
         :key="index"
         class="slider-card">
      <img :src="slide.image" :alt="slide.title">
    </div>
  </div>
  <div class="slider-info">
    <h1>{{selectedSlide.title}}</h1>
    <p>{{selectedSlide.description}}</p>
    <button class="slider-button">BOOK</button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

快完成了
从外观上看,这似乎已经接近完工了,但我们仍然需要……

互动

如果我们把滑块的交互分解开来,基本上可以分为三个动作:按下卡片、移动卡片和松开卡片。为了追踪这些动作,我们需要将 `push`、`move` 和 `save` 绑定@mouseDown@mouseUpVue@mouseMove实例中的方法。此外,为了防止图片出现重影,它们应该具有 `images_phost` 属性draggable=false

<div id="slider" class="slider" @mouseMove="mouseMoving">
  <div class="slider-cards">
    <div @mouseDown="startDrag"
         @mouseUp="stopDrag"
         v-for="(slide, index) in slides" 
         :key="index"
         class="slider-card">
      <img :src="slide.image" :alt="slide.title" draggable="false">
    </div>
  </div>
  <!-- slider info and the rest -->
Enter fullscreen mode Exit fullscreen mode

现在我们需要在 Vue 端创建这些方法,同时还要在数据对象中添加几个变量:

  data: {
    // ... other variables
    dragging: false,
    initialMouseX: 0,
    initialCardsX: 0,
    cardsX: 0
  },
  methods: {
    startDrag (e) {

    },
    stopDrag () {

    },
    mouseMoving (e) {

    }
  }
Enter fullscreen mode Exit fullscreen mode

这三个方法都会接收一个事件(这里我们称之为 `event` e),但我们只需要在 ` startDragand`mouseMoving方法中使用它。
接下来的代码片段将逐步讲解如何填充这三个方法,因此我将忽略其余代码。

首先,我们需要根据鼠标操作将其设置draggingtruefalse :

startDrag (e) {
  this.dragging = true
},
stopDrag () {
  this.dragging = false
},
mouseMoving (e) {

}
Enter fullscreen mode Exit fullscreen mode

很简单,现在我们希望只有在拖动卡片时才能移动它们,所以在mouseMoving方法内部我们将添加以下条件:

startDrag (e) {
  this.dragging = true
},
stopDrag () {
  this.dragging = false
},
mouseMoving (e) {
  if(this.dragging) {

  }
}
Enter fullscreen mode Exit fullscreen mode

好了,现在事情变得有趣起来了,我们需要跟踪开始拖动时卡片和鼠标的位置,该pageX属性将告诉我们鼠标的位置,而cardsX我们的数据内部将跟踪卡片容器的位置:

startDrag (e) {
  this.dragging = true
  this.initialMouseX = e.pageX
  this.initialCardsX = this.cardsX
},
stopDrag () {
  this.dragging = false
},
mouseMoving (e) {
  if(this.dragging) {

  }
}
Enter fullscreen mode Exit fullscreen mode

在存储了卡片和鼠标的初始 X 坐标后,我们可以通过计算方法执行时鼠标位置的差异来推断卡片容器的目标位置,mouseMoving如下所示:

startDrag (e) {
  this.dragging = true
  this.initialMouseX = e.pageX
  this.initialCardsX = this.cardsX
},
stopDrag () {
  this.dragging = false
},
mouseMoving (e) {
  if(this.dragging) {
    const dragAmount = e.pageX - this.initialMouseX
    const targetX = this.initialCardsX + dragAmount
    this.cardsX = targetX
  }
}
Enter fullscreen mode Exit fullscreen mode

我们的组件几乎已经准备就绪,只需要找到一种方法将卡片容器绑定到该cardsX属性,这可以通过在 HTML 中添加该属性来实现:

...
<div class="slider-cards" :style="`transform: translate3d(${cardsX}px,0,0)`">
...
Enter fullscreen mode Exit fullscreen mode

你可能会问:“为什么使用 translate3d 而不是普通的 2D 平移?”,原因是 translate3dtranslate3d具有硬件加速功能,通常性能更佳。你可以在这个网站上自行验证。

幻灯片现在可以播放了,但是有个小问题:当我们松手时,它们会停留在我们放下的位置,而且影片信息也不会改变。我们真正需要的是让它们找到最近的幻灯片并将其居中显示。

要找到最近的幻灯片,我们只需将当前位置除以卡片的宽度,然后对结果进行四舍五入。之后,我们将使用 TweenLite 将卡片动画移动到相应的位置:

stopDrag () {
  this.dragging = false

  const cardWidth = 290
  const nearestSlide = -Math.round(this.cardsX / cardWidth)
  this.selectedIndex = Math.min(Math.max(0, nearestSlide), this.slides.length -1)
  TweenLite.to(this, 0.3, {cardsX: -this.selectedIndex * cardWidth})
}
Enter fullscreen mode Exit fullscreen mode

为了更好地理解这个公式,这个 GIF 动画展示了该cardsX值与nearestSlide.
最近的幻灯片

现在公布最终结果!

目前它仅适用于桌面设备,但这或许可以通过某种方式解决,您可以在这篇文章vue-touch中了解更多信息。

以上就是本周第三个小部件的介绍

如果你还没看过之前的那个,链接在这里

如果您希望下周看到某个特定的小部件,请在评论区留言。

文章来源:https://dev.to/ederchrono/animated-card-slider-with-vue--gsap---wotw-45bn