Use isolation style fullscreen gradient
This commit is contained in:
1 parent
29b6418e2c
commit
3001267393
1 file changed
+54
-20
@@ -7,12 +7,15 @@ import {
|
|||||||
} from "./palette"
|
} from "./palette"
|
||||||
|
|
||||||
const props = defineProps<{ src: string; active: boolean }>()
|
const props = defineProps<{ src: string; active: boolean }>()
|
||||||
|
const ANIMATION_SPEED = 0.3
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||||
const palette = ref<AlbumPalette | null>(null)
|
const palette = ref<AlbumPalette | null>(null)
|
||||||
let frame = 0
|
let frame = 0
|
||||||
let startedAt = 0
|
let startedAt = 0
|
||||||
let lastFrameAt = 0
|
let lastFrameAt = 0
|
||||||
let random = [0, 0]
|
let random = [0, 0]
|
||||||
|
let angleJitter = 0
|
||||||
|
let flowParams: [number, number, number, number] = [5, 25, 0.75, -0.08]
|
||||||
|
|
||||||
const fallback: AlbumPalette = { dominant: [30, 35, 55], accent: [105, 65, 45] }
|
const fallback: AlbumPalette = { dominant: [30, 35, 55], accent: [105, 65, 45] }
|
||||||
|
|
||||||
@@ -26,18 +29,22 @@ function loadArtwork() {
|
|||||||
setPalette(fallback)
|
setPalette(fallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
image.onerror = () => { setPalette(fallback) }
|
image.onerror = () => {
|
||||||
|
setPalette(fallback)
|
||||||
|
}
|
||||||
image.src = props.src
|
image.src = props.src
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPalette(next: AlbumPalette) {
|
function setPalette(next: AlbumPalette) {
|
||||||
palette.value = next
|
palette.value = next
|
||||||
random = Array.from({ length: 2 }, () => Math.random() * Math.PI * 2)
|
random = Array.from({ length: 2 }, () => Math.random() * Math.PI * 2)
|
||||||
}
|
angleJitter = (Math.random() - 0.5) * 0.3
|
||||||
|
flowParams = [
|
||||||
function smoothstep(edge0: number, edge1: number, value: number) {
|
4.5 + Math.random(),
|
||||||
const amount = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)))
|
22 + Math.random() * 7,
|
||||||
return amount * amount * (3 - 2 * amount)
|
(0.65 + Math.random() * 0.2) * (Math.random() < 0.5 ? -1 : 1),
|
||||||
|
((-5 + (Math.random() - 0.5) * 12) * Math.PI) / 180,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function gradientHash(x: number, y: number): [number, number] {
|
function gradientHash(x: number, y: number): [number, number] {
|
||||||
@@ -68,6 +75,33 @@ function gradientNoise(x: number, y: number) {
|
|||||||
return 0.5 + 0.5 * (lower * (1 - easeY) + upper * easeY)
|
return 0.5 + 0.5 * (lower * (1 - easeY) + upper * easeY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function smoothstep(edge0: number, edge1: number, value: number) {
|
||||||
|
const amount = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)))
|
||||||
|
return amount * amount * (3 - 2 * amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
function flowPoint(x: number, y: number, time: number): [number, number] {
|
||||||
|
const degree = gradientNoise(
|
||||||
|
time * 0.1 + random[0] * 0.07,
|
||||||
|
x * y + random[1] * 0.07,
|
||||||
|
)
|
||||||
|
const angle = (((degree - 0.5) * 720 + 180) * Math.PI) / 180 + angleJitter
|
||||||
|
const sine = Math.sin(angle)
|
||||||
|
const cosine = Math.cos(angle)
|
||||||
|
let flowX = x * cosine - y * sine
|
||||||
|
let flowY = x * sine + y * cosine
|
||||||
|
const speed = time * flowParams[2]
|
||||||
|
flowX += Math.sin(flowY * flowParams[0] + speed) / flowParams[1]
|
||||||
|
flowY +=
|
||||||
|
Math.sin(flowX * flowParams[0] * 1.5 + speed) / (flowParams[1] * 0.5)
|
||||||
|
const tiltSine = Math.sin(flowParams[3])
|
||||||
|
const tiltCosine = Math.cos(flowParams[3])
|
||||||
|
return [
|
||||||
|
flowX * tiltCosine - flowY * tiltSine,
|
||||||
|
flowX * tiltSine + flowY * tiltCosine,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
function draw(now: number) {
|
function draw(now: number) {
|
||||||
const element = canvas.value
|
const element = canvas.value
|
||||||
const context = element?.getContext("2d")
|
const context = element?.getContext("2d")
|
||||||
@@ -80,7 +114,12 @@ function draw(now: number) {
|
|||||||
lastFrameAt = now
|
lastFrameAt = now
|
||||||
const time = (now - startedAt) / 1000
|
const time = (now - startedAt) / 1000
|
||||||
const width = 112
|
const width = 112
|
||||||
const height = Math.max(72, Math.round(width * element.clientHeight / Math.max(1, element.clientWidth)))
|
const height = Math.max(
|
||||||
|
72,
|
||||||
|
Math.round(
|
||||||
|
(width * element.clientHeight) / Math.max(1, element.clientWidth),
|
||||||
|
),
|
||||||
|
)
|
||||||
if (element.width !== width || element.height !== height) {
|
if (element.width !== width || element.height !== height) {
|
||||||
element.width = width
|
element.width = width
|
||||||
element.height = height
|
element.height = height
|
||||||
@@ -89,21 +128,13 @@ function draw(now: number) {
|
|||||||
const colors = palette.value ?? fallback
|
const colors = palette.value ?? fallback
|
||||||
const dominant = toOkLab(colors.dominant)
|
const dominant = toOkLab(colors.dominant)
|
||||||
const accent = toOkLab(colors.accent)
|
const accent = toOkLab(colors.accent)
|
||||||
const motionTime = time * 0.2
|
const motionTime = time * ANIMATION_SPEED
|
||||||
const gradientTime = motionTime * 0.1
|
|
||||||
const centerX = 0.5 + 0.43 * Math.sin(motionTime * 0.22)
|
|
||||||
const centerY = 0.5 + 0.4 * Math.sin(motionTime * 0.31 + 1.4)
|
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
for (let x = 0; x < width; x++) {
|
for (let x = 0; x < width; x++) {
|
||||||
const px = x / (width - 1)
|
const px = x / (width - 1)
|
||||||
const py = y / (height - 1)
|
const py = y / (height - 1)
|
||||||
const degree = gradientNoise(
|
const point = flowPoint(px - 0.5, py - 0.5, motionTime)
|
||||||
gradientTime + random[0] * 0.07,
|
const blend = smoothstep(-0.3, 0.2, point[0])
|
||||||
px * py + random[1] * 0.07,
|
|
||||||
)
|
|
||||||
const distance = Math.hypot(px - centerX, py - centerY)
|
|
||||||
const radius = distance + (degree - 0.5) * 0.12
|
|
||||||
const blend = 1 - smoothstep(0.3, 0.52, radius)
|
|
||||||
const lab = dominant.map(
|
const lab = dominant.map(
|
||||||
(value, channel) =>
|
(value, channel) =>
|
||||||
value * (1 - blend) + accent[channel] * blend,
|
value * (1 - blend) + accent[channel] * blend,
|
||||||
@@ -121,7 +152,9 @@ function draw(now: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.src, loadArtwork)
|
watch(() => props.src, loadArtwork)
|
||||||
watch(() => props.active, (active) => {
|
watch(
|
||||||
|
() => props.active,
|
||||||
|
(active) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
startedAt = 0
|
startedAt = 0
|
||||||
lastFrameAt = 0
|
lastFrameAt = 0
|
||||||
@@ -129,7 +162,8 @@ watch(() => props.active, (active) => {
|
|||||||
} else {
|
} else {
|
||||||
cancelAnimationFrame(frame)
|
cancelAnimationFrame(frame)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadArtwork()
|
loadArtwork()
|
||||||
|
|||||||
Reference in new issue
Block a user