项目正在快速迭代中,API 与视觉细节可能随版本调整,请勿用于生产环境。开发迭代中,请勿用于生产环境。
Skip to content

滚动叙事 ​

高端产品页的核心手法:滚动条就是时间轴。向下滚动,笔记本缓缓开盖、 参数逐条亮起;向上滚动,整个过程原样回溯——动画进度与滚动位置严格同步, 不抢滚动条的 control,也没有「播动画时页面停住」的割裂感。

Evoke UI 把它拆成两个形态,与 v-reveal 的触发型入场互补:

  • 触发型(v-reveal):元素进视口 → 播一次。适合入场点缀;
  • 进度驱动型(本页):元素在视口里的穿越程度 = 动画进度,双向可逆。适合产品叙事。

本站就在实战使用:首页的「一个官网的诞生」滚动带、企业官网案例 的「数据链路」折线(stroke-dashoffset 随滚动画出折线),都是 EvScrollScene 做的, 源码就在页面里。

EvScrollScene 场景 ​

EvScrollScene 外层按 duration 拉出滚动长度,内层 sticky 钉在视口里; 进度经作用域插槽 { progress } 与 CSS 变量 --ev-scene-progress(0..1)双通道暴露。 纯 CSS 消费用 calc()——笔记本开合就是一行 transform:

随滚动开合的笔记本
缓慢向下滚动:屏幕随开盖角度渐亮,标题浮出;向上滚动整个过程原样回溯。

继续向下滚动 ↓

开盖,即赴工作状态

12 亿色原彩屏 · 自适应 120Hz

厚度 11.5mm · 全天续航

vue
<EvScrollScene :duration="260">
  <template #default>
    <div class="sl-lid">
      <!-- 闭合 -92° → 直立 0°,滚动条就是时间轴 -->
      <div class="sl-screen">…</div>
    </div>
  </template>
</EvScrollScene>

<style>
.sl-lid {
  transform-origin: 50% 100%;
  transform: rotateX(calc(-92deg + var(--ev-scene-progress) * 92deg));
}
.sl-screen {
  opacity: calc(var(--ev-scene-progress) * 1.4 - 0.25);
}
</style>

要点:

  • 屏幕亮起用 opacity: calc(var(--ev-scene-progress) * 1.4 - 0.25)——calc 超界自动收敛到 0..1;
  • 只动 transform 与 opacity(合成器层),滚动全程不掉帧;
  • 用户系统开了「减少动态效果」时,组件自动把进度钉在终态 1,页面静态呈现开盖完成的样子, 不需要宿主写任何判断;
  • JS 消费(canvas 图片序列刷帧、video.currentTime = progress × 时长)走插槽值, 与 CSS 变量同源同值。

进度本身是线性的。想要「缓动」的呼吸感,在消费端对进度做一次曲线映射即可——把区段 归一后过 easeOutCubic,再绑回样式(本站首页「一个官网的诞生」与企业官网案例的 「数据链路」就是这个做法,源码即参考):

js
// 区段 [a, b] 归一到 0..1,再过 easeOutCubic
const seg = (p, a, b) => Math.min(Math.max((p - a) / (b - a), 0), 1)
const easeOut = (t) => 1 - Math.pow(1 - t, 3)
// opacity: easeOut(seg(progress, 0.34, 0.62))

分步功能区 ​

「滚一下进入下一个功能模块」用原生 scroll-snap 实现:吸附是浏览器原生行为, 温和可打断,滚轮、触摸板、键盘、读屏全都自然工作——不要做滚轮劫持式的整页翻页。

容器加 ev-snap-y(proximity 温和吸附)、每个分栏加 ev-snap-start:

分步功能介绍
在下面的演示窗格内滚动,功能栏会轻吸到窗格顶部;再向下滚进入下一栏,向上滚回溯。
01

一屏一个重点

proximity 模式只在靠近吸附位时轻推一下,中间浏览不受打扰。

02

原生滚动,无劫持

滚轮、触摸板惯性、键盘与读屏全部保持浏览器默认行为。

03

双向可逆

向上滚即回到上一栏,与触发型入场动画的分工见上。

html
<div class="snap-demo ev-snap-y">
  <section class="snap-pane ev-snap-start">…</section>
  <section class="snap-pane ev-snap-start">…</section>
  <section class="snap-pane ev-snap-start">…</section>
</div>

整页官网的分步(每个 EvSection 吸到视口顶)不用手写:给区块加 snap prop 即可, 任一区块声明后页面滚动容器自动启用吸附;悬浮顶栏用 CSS scroll-margin-top 让位:

vue
<EvSection eyebrow="Features" title="即时洞察" snap>
  …
</EvSection>

自定义场景 ​

场景结构不满足时(视差层、多元素错拍),用 useScrollProgress 进度原语自行组装, 返回 { progress, reduced },几何语义与组件一致:

vue
<script setup>
import { ref } from 'vue'
import { useScrollProgress } from '@wil-works/evoke-ui'

const stage = ref(null)
const { progress } = useScrollProgress(stage)
</script>

<template>
  <div ref="stage">
    <div :style="{ transform: `translateY(${progress * -40}px)` }">视差层</div>
  </div>
</template>

基于 MIT 许可证发布