mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
feat: hero animation plays on load in two-column layout
- Replace scroll-scrubbed animation with time-based gsap tween - Loop infinitely at constant linear rate (4s per cycle) - Switch to side-by-side layout: logo left (60%), text right (40%) - Stacks vertically on mobile Amp-Thread-ID: https://ampcode.com/threads/T-019d92fc-1970-7699-928b-f03cff3ed120 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -1,48 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { useFrameScrub } from '../../composables/useFrameScrub'
|
||||
import { prefersReducedMotion } from '../../composables/useReducedMotion'
|
||||
import type { Locale } from '../../i18n/translations'
|
||||
import { t } from '../../i18n/translations'
|
||||
import { gsap } from '../../scripts/gsapSetup'
|
||||
|
||||
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
|
||||
useFrameScrub(canvasRef, {
|
||||
frameCount: 75,
|
||||
frameSrc: (i) =>
|
||||
`/videos/hero-logo-seq/Logo${String(i).padStart(2, '0')}.webp`,
|
||||
scrollTrigger: (canvas) => ({
|
||||
trigger: document.documentElement,
|
||||
start: 'top top',
|
||||
end: () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
return `+=${rect.bottom + window.scrollY}`
|
||||
},
|
||||
scrub: 0.3
|
||||
const FRAME_COUNT = 75
|
||||
const images: HTMLImageElement[] = []
|
||||
let ctx: gsap.Context | undefined
|
||||
|
||||
onMounted(() => {
|
||||
if (!canvasRef.value || prefersReducedMotion()) return
|
||||
const canvas = canvasRef.value
|
||||
const draw = canvas.getContext('2d')
|
||||
if (!draw) return
|
||||
|
||||
let loadedCount = 0
|
||||
|
||||
function drawFrame(frame: number) {
|
||||
const index = Math.round(frame)
|
||||
const img = images[index]
|
||||
if (!img || !draw) return
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
draw.drawImage(img, 0, 0)
|
||||
}
|
||||
|
||||
function onFrameReady() {
|
||||
loadedCount++
|
||||
if (loadedCount === FRAME_COUNT) {
|
||||
drawFrame(0)
|
||||
const proxy = { frame: 0 }
|
||||
ctx = gsap.context(() => {
|
||||
gsap.to(proxy, {
|
||||
frame: FRAME_COUNT - 1,
|
||||
duration: 4,
|
||||
ease: 'none',
|
||||
repeat: -1,
|
||||
onUpdate() {
|
||||
drawFrame(proxy.frame)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < FRAME_COUNT; i++) {
|
||||
const img = new Image()
|
||||
img.src = `/videos/hero-logo-seq/Logo${String(i).padStart(2, '0')}.webp`
|
||||
img.onload = onFrameReady
|
||||
img.onerror = onFrameReady
|
||||
images.push(img)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
images.forEach((img) => {
|
||||
img.onload = null
|
||||
img.onerror = null
|
||||
})
|
||||
images.length = 0
|
||||
ctx?.revert()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="flex flex-col items-center px-4 py-16 lg:px-20 lg:pt-18 lg:pb-8"
|
||||
class="relative flex min-h-screen flex-col lg:flex-row lg:items-center"
|
||||
>
|
||||
<h1
|
||||
class="text-primary-comfy-canvas text-center text-5xl font-light whitespace-pre-line lg:text-8xl"
|
||||
>
|
||||
{{ t('hero.title', locale) }}
|
||||
</h1>
|
||||
|
||||
<div class="mt-12 w-full max-w-3xl overflow-hidden rounded-2xl">
|
||||
<div class="relative w-full lg:w-3/5">
|
||||
<canvas ref="canvasRef" class="w-full" />
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="text-primary-comfy-canvas mt-10 max-w-2xl text-center text-sm/relaxed lg:text-base"
|
||||
>
|
||||
{{ t('hero.subtitle', locale) }}
|
||||
</p>
|
||||
<div class="px-6 py-12 lg:w-2/5 lg:px-16">
|
||||
<h1
|
||||
class="text-primary-comfy-canvas text-4xl font-light whitespace-pre-line lg:text-6xl"
|
||||
>
|
||||
{{ t('hero.title', locale) }}
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="text-primary-comfy-canvas mt-8 max-w-lg text-sm/relaxed lg:text-base"
|
||||
>
|
||||
{{ t('hero.subtitle', locale) }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user