想用纯CSS复刻Tinder左滑右滑?这个蜘蛛猪卡片实验带你搞定

3,211字
14–20 分钟
in

这年头刷短视频刷到停不下来,连找对象都得靠左滑右滑。但有没有想过,这种丝滑的滑动交互,不用一行JavaScript,光靠CSS能不能整出来?还真有人这么干了——Web-Slinger.css这个纯CSS动画库本来只搞垂直滚动触发,结果被魔改成了水平滑动判断喜欢还是讨厌。下面就把这个蜘蛛猪卡片滑动器的折腾过程掰开揉碎,让小白也能看明白。

目录

啥是纯CSS滑动

所谓纯CSS滑动,就是只用层叠样式表里的滚动驱动动画来实现卡片被划走的效果。Web-Slinger.css原本是替代哇哦.js的玩具,专门监听页面上下滚动来触发动画。但Tinder那种左右划卡,本质上是水平滚动。所以得先给这个库打补丁,让它的触发器从垂直轴改成水平轴。

[class^="scroll-trigger-"] {
  view-timeline-axis: x;
}

上面这段代码把默认的垂直滚动轴(y)掰成了水平轴(x)。不然的话,那些触发器因为容器宽度被挤到视口外面,但垂直方向其实一开始就在屏幕里,会直接全部跑出来,整个效果就崩了。

搭建滑动环境

从网上找个现成的JavaScript滑动演示库(比如Nikolay Talanov那个),把所有JS代码删干净,卡片只留一张。然后把Web-Slinger.css拽进来,打上刚才的水平补丁。接着把放卡片的容器设成固定定位(position: fixed),再搞三个滚动吸附盒子并排排好,每个都跟视口一样宽高。

<main>
  <div class="slide"></div>
  <div id="middle" class="slide"></div>
  <div class="slide">
    <div class="scroll-trigger-1"></div>
  </div>
</main>

中间那个滑块的scroll-align设成center,这样页面一加载就停在中间,往前滚是左滑(不喜欢),往后滚是右滑(喜欢)。有个小坑得注意:滚动元素不需要真的显示滚动条,就像复选框黑魔法里先把复选框藏起来、用label假装按钮一样。这里只是借用滚动驱动的行为,默认的滚动条UI完全可以干掉。

左滑不喜欢咋做

把带scroll-trigger-1的div塞到第三张幻灯片里,用它触发卡片上的拒绝动画。

<div class="demo__card on-scroll-trigger-1 reject">
  <!-- 蜘蛛猪的图片和文字 -->
</div>

当页面往前滚动(相当于左滑),.scroll-trigger-1进入视口,那个reject类就开始干活。这个逻辑跑起来很顺,就像白嫖了一个正版。

右滑喜欢的翻车现场

同样的套路复制一份,把类名换成scroll-trigger-2,放到第一张幻灯片里。

<div class="on-scroll-trigger-2 accept">
  <div class="demo__card on-scroll-trigger-2 reject"></div>
</div>

结果傻眼了——蜘蛛猪卡片一加载就自动被“喜欢”了。这就好比相亲软件还没划呢,系统直接替人说“我愿意”。为啥左滑没问题,右滑就翻车?因为滚动驱动动画的设计逻辑是:元素在滚动方向上穿过视口时完成动画。右滑需要向后滚动(从中间滚回第一张),但浏览器默认只管向前滚,一加载时那个触发器已经在视口里(因为第一张在左边),动画直接跑完了。

解决自动喜欢的骚操作

幸好Chrome开发团队有位大佬Bramus整了个检测滚动方向的CSS黑魔法。核心是用自定义属性–scroll-direction配合样式查询,让右滑触发器在正确的时机才现身。

:root {
  animation: adjust-slide-index 3s steps(3, end), adjust-pos 1s;
  animation-timeline: scroll(root x);
}

@property --slide-index {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@keyframes adjust-slide-index {
  to {
    --slide-index: 3;
  }
}

.scroll-trigger-2 {
  display: none;
}

@container style(--scroll-direction: -1) and style(--slide-index: 0) {
  .scroll-trigger-2 {
    display: block;
  }
}

这里解释一下:–slide-index通过一个3秒的滚动驱动动画来计数当前在第几张幻灯片。样式查询的意思是,只有当滚动方向是向后(-1)并且处于第一张幻灯片(索引0)时,.scroll-trigger-2才会被渲染出来。这样一来,必须得实打实地从中间往左划(向后滚)到第一张,卡片才会被标记为喜欢。稍微手抖往回滚一点点?不好意思,触发器不出现,动画不执行。

统计喜欢和讨厌的人数

为了看看到底多少人爱蜘蛛猪、多少人嫌它丑,搞了两个第三方计数器图片当背景。用样式查询分别控制显示哪个计数器和描述文字。

@container style(--scroll-trigger-1: 1) {
  .result {
    background-image: url('https://counter6.optistats.ovh/private/freecounterstat.php?c=qbgw71kxx1stgsf5shmwrb2aflk5wecz');
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
  }
  .counter-description::after {
    content: 'who like spider pig';
  }
  .scroll-trigger-2 {
    display: none;
  }
}

@container style(--scroll-trigger-2: 1) {
  .result {
    background-image: url('https://counter6.optistats.ovh/private/freecounterstat.php?c=abtwsn99snah6wq42nhnsmbp6pxbrwtj');
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
  }
  .counter-description::after {
    content: 'who dislike spider pig';
  }
  .scroll-trigger-1 {
    display: none;
  }
}

左滑触发–scroll-trigger-1变成1,显示讨厌计数器;右滑触发–scroll-trigger-2变成1,显示喜欢计数器。同时把对方的触发器藏掉,防止重复计数。

备胎方案:不用样式查询的简化版

如果觉得样式查询兼容性还不够广(目前只有Chromium系浏览器支持),还有一个更土但更稳的办法:直接用两个独立的滚动容器。一个管左滑,一个管右滑,中间用绝对定位叠在一起。然后通过监测滚动容器的scroll事件(虽然说了不用JS,但这里可以稍微开个后门)来切换显示。不过纯CSS党肯定瞧不上这种,所以这里还是回到纯CSS路线。

方案优点缺点
样式查询+滚动方向纯CSS,无额外依赖浏览器兼容性窄
独立滚动容器+JS兼容性好需要几行JS监听

最终这个蜘蛛猪滑动器跑起来的效果:Chrome和Edge里打开,划左划右都能正确记录态度。虽然比预想的多费了不少头发,但证明了CSS规范里藏着的能量超乎想象。哪天浏览器能给个view-timeline-direction属性,直接指定向后滚动才触发,那就不用这么魔改了。