跳转到正文
jolt.
← 动效配方

运动 / iOS / NATIVESCRIPT VUE

属于你的节奏。

描绘信号、轻柔标题与依次展开的 Vena 界面。

来自 Vena ↗
原生录屏 · iOSVena iOS 创作者录屏:英文界面、开场、粒子心跳、节奏收藏与心脏聚焦。原始速度,60 fps 导出,无音频;省略最初 3.25 秒的系统主屏幕与启动。录制设备未说明;非性能测试。BPM 数值仅为示意。 BodyParts3D © DBCLS · CC BY 4.0 / MakeHuman · CC0 ↗

动效背后的代码。

Jolt 0.1.0-rc.11

来自项目的完整源文件,固定到已核对的版本。请结合下面的依赖说明进行适配。

需要 NativeScript-Vue 3 和 Jolt RC.11。向 opening 传入四个已加载的原生视图;title、caption、skip 初始 opacity 为 0。rule 承载 190×30 路径。WelcomeOverlay.vue 展示完成、跳过和清理保护。revealInterface 接收自有 MotionScope 及四个已挂载视图。卸载时清理 opening 返回对象和界面 scope。此文件只负责文字、路径与界面入场;粒子心脏需要 HeartScene 及其资源。保留减少动态处理。录屏平台为 iOS。

entrance.ts
import type { View } from '@nativescript/core'
import { motionScope, reducedMotion, set, type MotionScope } from '@rafaelaugusto/jolt'
import { shape, type Shape } from '@rafaelaugusto/jolt/shapes'

export interface EntranceTargets { title: View; caption: View; rule: View; skip: View }

export function opening(targets: EntranceTargets) {
  const scope = motionScope()
  const drawings: Shape[] = []
  if (reducedMotion()) {
    set([targets.title, targets.caption, targets.skip], { opacity: 1 })
    return { scope, finished: scope.animate(targets.title, { opacity: [1, 1] }, { duration: 180 }).finished, dispose: () => scope.dispose() }
  }
  const line = shape(targets.rule, { viewBox: [0, 0, 190, 30], d: 'M0 15 L47 15 L56 12 L62 15 L76 15 L82 21 L89 2 L97 28 L105 15 L123 15 Q134 5 144 15 L190 15', fill: 'transparent', stroke: '#FF4067', strokeWidth: 1.1, strokeEnd: 0, lineCap: 'round' })
  drawings.push(line)
  const sequence = scope.timeline([
    [targets.title, { opacity: [0, 1], y: [14, 0] }, { at: 420, duration: 800, ease: 'easeOut' }],
    { at: 700, run: () => line.animate({ strokeEnd: 1 }, { duration: 1000, ease: 'easeInOut' }) },
    [targets.caption, { opacity: [0, 1], y: [8, 0] }, { at: 1000, duration: 600, ease: 'easeOut' }],
    [targets.skip, { opacity: [0, .7] }, { at: 1200, duration: 500 }],
    [targets.title, { opacity: [1, 1] }, { at: 2100, duration: 350 }],
  ])
  return { scope, finished: sequence.finished, dispose() { scope.dispose(); drawings.forEach(drawing => drawing.dispose()) } }
}

export function revealInterface(scope: MotionScope, header: View, card: View, footer: View, hint: View) {
  return scope.timeline([
    [header, { opacity: [0, 1], y: reducedMotion() ? 0 : [-9, 0] }, { at: 0, duration: 500, ease: 'easeOut' }],
    [card, { opacity: [0, 1], y: reducedMotion() ? 0 : [38, 0] }, { at: 100, type: 'spring', duration: 850, bounce: .12 }],
    [footer, { opacity: [0, 1], y: reducedMotion() ? 0 : [12, 0] }, { at: 230, duration: 600, ease: 'easeOut' }],
    [hint, { opacity: [0, .7] }, { at: 600, duration: 600 }],
  ])
}
查看完整示例 ↗

阅读完整指南 ↗