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

运动 / iOS / NATIVESCRIPT VUE

开场的小魔法。

魔药轻浮,印记绘出,柔和揭开应用。

来自 Hexdrop ↗
原生录屏 · iOSHexdrop iOS 开场:创作者录屏中 3.8 至 7.0 秒的片段。悬浮图案、印记描边、粒子、光波与应用揭示。英文界面,原始速度,无音频。录制设备未说明;非性能测试。

动效背后的代码。

Jolt 0.1.0-rc.11

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

需要 NativeScript-Vue 3 和 Jolt RC.11。提供挂载并测量后的原生 GridLayout、LaunchScreen.vue 的子视图 ID、样式和 hexdrop-mark.png。绘图表面保持固定边界,变换其容器。reveal 回调挂载内容并等待 nextTick 后淡出遮罩。保留返回句柄,等待 finished,永久卸载时调用 dispose;保留减少动态效果和故障回退分支。系统启动屏是静态的;NativeScript 就绪后才播放此时间线。仅验证 iOS 模拟器。

launch-animation.ts
import type { GridLayout, View } from '@nativescript/core'
import { motionScope, particles, reducedMotion, set } from '@rafaelaugusto/jolt'
import { shape, type Shape } from '@rafaelaugusto/jolt/shapes'

const SEAL = 'M128 10 L230 69 L230 187 L128 246 L26 187 L26 69 Z'

/** A single, pausable entrance. Native launch artwork uses the same resting pose. */
export function playLaunch(root: GridLayout, reveal: () => Promise<void>) {
  const motion = motionScope()
  const drawings: Shape[] = []
  const view = (id: string) => root.getViewById<View>(id)
  const mark = view('launch-mark')
  const halo = view('launch-halo')
  const seal = view('launch-seal')
  const flash = view('launch-flash')
  const title = view('launch-title')
  const caption = view('launch-caption')

  const finished = (async () => {
    if (reducedMotion()) {
      set([title, caption], { opacity: 1 })
      if (!await motion.animate(root, { opacity: [1, 1] }, { duration: 100 }).finished) return false
    } else {
      // Transform containers, keeping the native drawing surfaces at fixed bounds.
      motion.gradient(view('launch-halo-paint'), {
        type: 'radial', radius: 0.5,
        colors: ['rgba(131,238,61,0.28)', 'rgba(91,179,32,0.09)', 'rgba(10,9,14,0)'],
        stops: [0, 0.4, 1],
      })
      motion.gradient(view('launch-flash-paint'), {
        type: 'radial', radius: 0.5,
        colors: ['rgba(207,255,154,0)', 'rgba(172,255,92,0.20)', 'rgba(10,9,14,0)'],
        stops: [0, 0.65, 1],
      })
      const outline = shape(view('launch-seal-paint'), {
        viewBox: [0, 0, 256, 256], d: SEAL,
        stroke: '#8ebc63', strokeWidth: 0.8, strokeEnd: 0,
        lineJoin: 'round',
      })
      drawings.push(outline)
      motion.use(() => particles(view('launch-motes'), {
        colors: ['#d9ffb1', '#b5f564', '#628941'], source: 'center',
        rate: 14, duration: 800, lifetime: 1100, size: 2,
        velocity: 58, velocityRange: 18, spread: 360, gravity: -7,
      }))

      const entrance = motion.timeline([
        [mark, { scale: [1, 1.08, 1.03, 1], y: [0, -10, -4, 0], rotate: [0, -5, 2, 0] },
          { at: 0, duration: 1550, times: [0, 0.35, 0.7, 1], ease: 'easeInOut' }],
        [halo, { opacity: [0, 0.9, 0.5], scale: [0.7, 1.15, 1] },
          { at: 0, duration: 1550, ease: 'easeInOut' }],
        [seal, { opacity: [0, 0.6, 0.6, 0], rotate: [-16, 0, 18], scale: [0.92, 1, 1.1] },
          { at: 0, duration: 1500, ease: 'easeInOut' }],
        { at: 80, run: () => outline.animate({ strokeEnd: 1 }, { duration: 900, ease: 'easeInOut' }) },
        [title, { opacity: [0, 1], y: [14, 0] }, { at: 300, duration: 540, ease: 'easeOut' }],
        [caption, { opacity: [0, 1], y: [8, 0] }, { at: 520, duration: 440, ease: 'easeOut' }],
        [flash, { opacity: [0, 1, 0], scale: [0.55, 1.2, 1.8] },
          { at: 820, duration: 650, ease: 'easeOut' }],
      ])
      if (!await entrance.finished) return false
    }

    if (motion.disposed) return false
    await reveal()
    if (motion.disposed) return false
    return motion.animate(root, { opacity: 0 }, {
      duration: reducedMotion() ? 150 : 320, ease: 'easeInOut',
    }).finished
  })()

  return {
    finished,
    dispose() {
      motion.dispose()
      drawings.forEach(drawing => drawing.dispose())
    },
  }
}
查看完整示例 ↗

阅读完整指南 ↗