制作滚动卡牌列表 - WotW
欢迎来到“每周小部件”系列,在这里我会选取一些很棒的 UI/UX 组件的 GIF 或视频,并用代码将它们变成现实。
今天我们将制作一个滚动时会动画展开的卡片列表。这个组件的灵感来源于Hiwow在Dribbble上创建的第一个部分,它的样子如下:
准备工作
今天的组件我们将只使用Vue.js,不使用任何动画库,这意味着我们将大量使用 Vue 的功能。
如果你想跟着做,可以 fork 这个已经包含所有依赖项的codepen 模板。
初始标记
为了使我们的应用正常运行,我们需要一个带有指定 id 的主 div,appVue.js 将挂载到该 div 上。完成之后,我们就可以开始创建卡片了。这里我只创建一个卡片,因为稍后我们将通过编程方式创建其余的卡片。
每张卡片都会有一个占位图片,图片旁边会有一个div我称之为卡片内容的元素。卡片内容会显示标题、描述和评分数据。
<div id="app">
<div class="card">
<img class="card__image" src="https://placeimg.com/100/140/animals">
<div class="card__content">
<h3>title</h3>
<p>description</p>
<div class="card__rating">
<span>8.0 </span>
<span class="card__stars--active">★★★</span>
<span class="card__stars--inactive">★★</span>
</div>
</div>
</div>
</div>
您可能已经注意到,我在类的命名中使用了BEM,这将有助于在下一步中设置卡片样式。
造型
现在我们有一张带有难看测试文字的图片,让我们来修改一下。首先,我们将直接在图片中添加一个浅灰色背景body。
body {
background-color: #FEFEFE;
}
然后,我们将为卡片声明一个预定义的高度,使其与图像高度相匹配140px。此外,我们还通过设置内边距、更改字体和添加阴影来添加一些细节,从而营造出卡片悬浮的效果。
.card {
height: 140px;
background-color: white;
padding: 5px;
margin-bottom: 10px;
font-family: Helvetica;
box-shadow: 0px 3px 8px 0px rgba(0,0,0,0.5);
}
卡片图片和卡片内容应并排显示display: inline-block。图片宽度为[此处应填写100px图片宽度],并留有少量边距以将其与文本分隔开,因此卡片内容将占据卡片剩余的宽度。
卡片内容的内部文本需要顶部对齐,否则显示效果会不理想。标题的默认边距h3过大,因此我们将对其进行调整0。
卡片评分容器需要底部对齐,我们将使用 `--align- items` 属性position: absolute来实现这一点。最后,星形span元素将根据星形是否处于“激活”状态而显示不同的颜色。
.card__img {
display: inline-block;
margin-right: 10px;
}
.card__content {
display: inline-block;
position: relative;
vertical-align: top;
width: calc(100% - 120px);
height: 140px;
}
.card__content h3 {
margin: 0;
}
.card__rating {
position: absolute;
bottom: 0;
}
.card__stars--active {
color: #41377C;
}
.card__stars--inactive {
color: #CCCCCC;
}
如果你足够细心,可能会注意到活跃恒星和非活跃恒星之间存在一定的间距差异。这是由于两个跨度元素之间的间距造成的,可以通过以下方式消除:
...
<div class="card__rating">
<span>8.0 </span>
<span class="card__stars--active">★★★</span><!-- I'm removing the space
--><span class="card__stars--inactive">★★</span>
</div>
...
这种行为
现在,在我们的 Vue 实例中,我们将开始声明组件需要使用的数据。我们需要很多卡片,但我没有逐个创建,而是只创建了三个,然后多次复制:
const cardsData = [
{
img:'https://placeimg.com/100/140/animals',
title: 'Title 1',
description: 'Tempora quam ducimus dolor animi magni culpa neque sit distinctio ipsa quos voluptates accusantium possimus earum rerum iure',
rating: 9.5,
stars: 4
},
{
img:'https://placeimg.com/100/140/arch',
title: 'Title 2',
description: 'Tempora quam ducimus dolor animi magni culpa neque sit distinctio ipsa quos voluptates accusantium possimus earum rerum iure',
rating: 8.4,
stars: 5
},
{
img:'https://placeimg.com/100/140/people',
title: 'Title 3',
description: 'Tempora quam ducimus dolor animi magni culpa neque sit distinctio ipsa quos voluptates accusantium possimus earum rerum iure',
rating: 7.234,
stars: 2
},
// copy and paste those three items as many times as you want
]
然后,在我们的 Vue 实例中,我们可以将该数组设置到 data 属性中,以便开始跟踪它。
new Vue({
el: '#app',
data: {
cards: cardsData
}
})
让我们将这些数据与 HTML 模板绑定。v-for我们将使用指令遍历卡片数据数组并渲染每个属性。
<div id="app">
<div class="card"
v-for="(card, index) in cards"
:key="index">
<img class="card__image" :src="card.img">
<div class="card__content">
<h3>{{card.title}}</h3>
<p>{{card.description}}</p>
<div class="card__rating">
<span>{{card.rating}} </span>
<span class="card__stars--active">{{card.stars}}</span>
<span class="card__stars--inactive">{{5 - card.stars}}</span>
</div>
</div>
</div>
</div>
不错,我们有很多卡片,可惜评分和星级与我们预期的不符。
正如你所看到的,星级评分的显示方式与数字相同,而且最后一个评分会显示多位小数。幸运的是,Vue.js 提供了一种名为过滤器(filters)的功能,可以帮助我们以想要的方式解析任何数据。
让我们回到 Vue 实例,声明两个过滤器,一个用于限制数字,另一个用于将任何数字转换为星号:
// ... data
filters: {
oneDecimal: function (value) {
return value.toFixed(1)
},
toStars: function (value) {
let result = ''
while(result.length < value) {
result+='★'
}
return result
}
},
// ...
有了这些筛选条件,我们就可以回到模板,将它们添加到需要筛选的数据中:
<!-- ... card markup -->
<span>{{card.rating | oneDecimal}} </span>
<span class="card__stars--active">{{card.stars | toStars }}</span><!--
--><span class="card__stars--inactive">{{5 - card.stars | toStars}}</span>
就这么简单{{ value | filter }},数据会在渲染前进行转换。
滚动
到目前为止,我们还没有为卡片列表添加任何行为,只是处理了它的外观和渲染方式。现在是时候添加动画效果了!
首先,我们需要以某种方式跟踪应用程序的滚动,为此我们将使用 Vue 的另一个特性:自定义指令。
这个滚动指令直接取自Vue.js 文档,当我们将其添加到我们的 JS 代码中时,就可以使用该v-scroll指令了:
Vue.directive('scroll', {
inserted: function (el, binding) {
let f = function (evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f)
}
}
window.addEventListener('scroll', f)
}
})
然后,只需在 HTML 代码中快速修改应用程序的 div 元素,即可使用它:
<div id="app" v-scroll="onScroll">
<!-- ... rest of the markup -->
现在我们应该能够创建onScroll用于跟踪滚动位置的方法了:
data: {
cards: cardsData,
scrollPosition: 0
},
methods: {
onScroll () {
this.scrollPosition = window.scrollY
}
},
请注意,我们添加了scrollPosition用于跟踪window.scrollY属性的参数。这有助于 Vue 在属性发生变化时重新计算。
动画卡片
在原版 Dribbble 中,卡片在移动到屏幕顶部时会有消失的scrollPosition效果。为了实现这个效果,我们需要在每次更新时重新计算每张卡片的样式。
接下来的两种方法会进行所有计算以生成样式。一开始可能会有点令人困惑,但我会尽力解释清楚。
首先,我们设置一个cardHeight常量,其值包含卡片的内边距和外边距。然后,根据卡片的索引,我们将其设置为positionY卡片的位置,第一个卡片对应0第二个位置160,然后是第三个位置320,依此类推。
之后我们需要知道卡片距离屏幕顶部的距离,我们计算出来并将值赋给变量deltaY。我们需要在卡片到达屏幕顶部时开始动画,所以我们只需要关注 deltaY 小于某个阈值的情况0。我将其限制在某个阈值和某个值之间-160,0因为当 deltaY 小于-160某个阈值时,卡片已经超出屏幕范围了。
dissapearingValue最后,我们创建一个yValue依赖zValue于dY值的元素。dissapearingValue顾名思义,该元素会使卡片淡入淡出,因此我们将其绑定到 CSS 的 opacity 属性。另外两个值将用于 transform 属性,使卡片看起来像是位于其他卡片的后面。
// ... methods
calculateCardStyle (card, index) {
const cardHeight = 160 // height + padding + margin
const positionY = index * cardHeight
const deltaY = positionY - this.scrollPosition
// constrain deltaY between -160 and 0
const dY = this.clamp(deltaY, -cardHeight, 0)
const dissapearingValue = (dY / cardHeight) + 1
const zValue = dY / cardHeight * 50
const yValue = dY / cardHeight * -20
card.style = {
opacity: dissapearingValue,
transform: `perspective(200px) translate3d(0,${yValue}px, ${zValue}px)`
}
return card
},
clamp (value, min, max) {
return Math.min(Math.max(min, value), max)
}
现在只需将每张卡片传递给该方法,并将结果作为名为styledCards的计算属性公开即可:
computed: {
styledCards () {
return this.cards.map(this.calculateCardStyle)
}
},
差不多完成了,让我们把新创建的样式绑定到卡片的HTML代码中:
<div class="card"
v-for="(card, index) in styledCards"
:style="card.style"
:key="index">
现在是最终结果(记得向下滚动):
本周的小工具介绍就到这里。
如果你还想了解更多,可以查看其他 WotW:
如果您希望下周看到某个特定的小部件,请在评论区留言。
文章来源:https://dev.to/ederchrono/making-a-scrolling-card-list---wotw-57ml



