梅花动画特效 #
restart
<script setup="props" lang="ts">
import { useRafFn } from '@vueuse/core'
import type { Fn } from '@vueuse/core'
const r180 = Math.PI
const r90 = Math.PI / 2
const r15 = Math.PI / 12
const color = '#88888850'
const el = ref<HTMLCanvasElement | null>(null)
const { random } = Math
const f = {
start: () => {},
}
const init = ref(5)
const len = ref(5)
const stopped = ref(false)
function initCanvas(canvas: HTMLCanvasElement, width = 400, height = 400, _dpi?: number) {
const ctx = canvas.getContext('2d')!
const dpr = window.devicePixelRatio || 1
// @ts-expect-error vendor prefix
const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1
const dpi = _dpi || dpr / bsr
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
canvas.width = dpi * width
canvas.height = dpi * height
ctx.scale(dpi, dpi)
return { ctx, dpi }
}
function polar2cart(x = 0, y = 0, r = 0, theta = 0) {
const dx = r * Math.cos(theta)
const dy = r * Math.sin(theta)
return [x + dx, y + dy]
}
onMounted(async () => {
const canvas = el.value!
const { ctx } = initCanvas(canvas)
const { width, height } = canvas
let steps: Fn[] = []
let prevSteps: Fn[] = []
let iterations = 0
const step = (x: number, y: number, rad: number) => {
const length = random() * len.value
const [nx, ny] = polar2cart(x, y, length, rad)
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(nx, ny)
ctx.stroke()
const rad1 = rad + random() * r15
const rad2 = rad - random() * r15
if (nx < -100 || nx > 500 || ny < -100 || ny > 500)
return
if (iterations <= init.value || random() > 0.5)
steps.push(() => step(nx, ny, rad1))
if (iterations <= init.value || random() > 0.5)
steps.push(() => step(nx, ny, rad2))
}
let controls: ReturnType<typeof useRafFn>
const frame = () => {
iterations += 1
prevSteps = steps
steps = []
if (!prevSteps.length) {
controls.pause()
stopped.value = true
}
prevSteps.forEach(i => i())
}
controls = useRafFn(frame, { immediate: false })
f.start = () => {
controls.pause()
iterations = 0
ctx.clearRect(0, 0, width, height)
ctx.lineWidth = 1
ctx.strokeStyle = color
prevSteps = []
steps = random() < 0.5
? [
() => step(0, random() * 400, 0),
() => step(400, random() * 400, r180),
]
: [
() => step(random() * 400, 0, r90),
() => step(random() * 400, 400, -r90),
]
controls.resume()
stopped.value = false
}
f.start()
})
</script>
<template>
<div>
<canvas ref="el" width="400" height="400" border />
<div hover:color-gray-600 duration-200 cursor-pointer mt-2 inline-block @click="f.start">
restart
</div>
</div>
</template>
文字显示动画 #
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<script setup>
const text
= 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
</script>
<template>
<div class="rounded p-2.5 text-gray-500 dark:text-white dark:bg-black">
<div class="relative mt-3">
<div>
{{ text }}
</div>
<div class="absolute inset-0">
<span class="reveal">
{{ text }}
</span>
</div>
</div>
</div>
</template>
<style scoped>
.reveal {
color: transparent;
background: linear-gradient(to right, transparent var(--p), #fff calc(var(--p) + 3em));
animation: reveal 6s linear infinite forwards;
}
html.dark .reveal {
background: linear-gradient(to right, transparent var(--p), #000 calc(var(--p) + 3em));
}
/* Houdini API */
@property --p {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
@keyframes reveal {
to {
--p: 100%;
}
}
</style>
渐层框线按钮 #
<template>
<div class="bg-black h-80 w-full flex items-center justify-center relative -z-2">
<button class="gredient relative text-white bg-transparent p-2px rounded-4px overflow-hidden">
<span class="block px-6 py-1 rounded-2px bg-black">EXAMPLE</span>
</button>
</div>
</template>
<style scoped>
.gredient::after {
content: "";
position: absolute;
top: -100px;
left: -100px;
right: -100px;
bottom: -100px;
z-index: -1;
background-image: conic-gradient(
from 0deg at 50% 50%,
#1e3a8a, /* 深蓝色 */
#3b82f6, /* 蓝色 */
#6366f1, /* 靛蓝色 */
#8b5cf6, /* 紫色 */
#a855f7, /* 紫罗兰色 */
#d946ef, /* 品红色 */
#ec4899, /* 粉红色 */
#6366f1, /* 靛蓝色 */
#3b82f6, /* 蓝色 */
#1e3a8a /* 深蓝色 */
);
animation: 3s rotate linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>