Skip to content

Commit

Permalink
Merge pull request #191 from oystersjp/feature/v2-hero
Browse files Browse the repository at this point in the history
[oysters.dev v2] 背景エフェクトの描画
  • Loading branch information
mii288 authored Sep 8, 2024
2 parents a5e92d7 + 18b223a commit bf01f0a
Show file tree
Hide file tree
Showing 9 changed files with 3,033 additions and 561 deletions.
3,314 changes: 2,760 additions & 554 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"astro": "^4.13.1",
"p5": "^1.10.0",
"tailwindcss": "^3.4.8",
"typescript": "^5.5.4"
},
Expand All @@ -34,6 +35,7 @@
"devDependencies": {
"@playwright/test": "^1.45.3",
"@types/eslint": "^9.6.0",
"@types/p5": "^1.7.6",
"@typescript-eslint/parser": "^8.0.1",
"eslint": "^8.57.0",
"eslint-plugin-astro": "^1.2.3",
Expand Down
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
27 changes: 27 additions & 0 deletions src/useCases/Home/components/Hero/Background.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
const attributes = Astro.props
---

<script>
import { Effect } from './assets/effect'

const getColor = (cssValueName: string): string =>
window
.getComputedStyle(document.querySelector('html') as HTMLElement)
.getPropertyValue(cssValueName)

new Effect({
selector: document.querySelector('#hero-background') as HTMLElement,
colors: {
line: getColor('--color-hero-visual-line'),
background: getColor('--color-gray-50')
},
enableDebug: import.meta.env.DEV
}).draw()
</script>
<div
id="hero-background"
class="relative size-full overflow-clip object-cover"
{...attributes}
>
</div>
11 changes: 4 additions & 7 deletions src/useCases/Home/components/Hero/Hero.astro
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
---
import playWithUs from './assets/playWithUs.svg?raw'
import { Svg } from '@/useCases/Svg'
import Background from './Background.astro'
---

<div class="relative h-[80vh]">
<img
src="https://dummyimage.com/376x505/efefef/3C7392"
alt=""
class="size-full object-cover"
/>
<div class="relative h-[120vw] max-h-[60vh]">
<Background aria-hidden="true" />
<div class="absolute left-0 top-0 size-full px-safe">
<div class="flex size-full flex-col">
<slot name="header" />
<div class="flex flex-auto items-center pl-4">
<div class="container mx-auto flex flex-auto items-center pl-4">
<Svg
src={playWithUs}
aria-hidden="true"
Expand Down
53 changes: 53 additions & 0 deletions src/useCases/Home/components/Hero/assets/effect/debug/Frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import p5 from 'p5'

export class Frame {
#p: p5

constructor(p: p5) {
this.#p = p
}

display() {
const p = this.#p

p.textSize(8)
p.textFont('monospace')

p.fill(200, 50, 50)
p.noStroke()
const diameter = 6

const left = { x: -p.width, label: '-p.width' }
const right = { x: 0, label: '0' }
const top = { y: 0, label: '0' }
const bottom = { y: p.height, label: 'p.height' }

// 左上
p.rect(left.x, top.y, diameter)
p.textAlign('left', 'top')
p.text(`(${left.label}, ${top.label})`, left.x + diameter, top.y + diameter)

// 右上
p.textAlign('right', 'top')
p.rect(right.x - diameter, top.y, diameter)
p.text(`(${right.x}, ${top.label})`, right.x - diameter, top.y + diameter)

// 右下
p.rect(right.x - diameter, bottom.y - diameter, diameter)
p.textAlign('right', 'bottom')
p.text(
`(${right.x}, ${bottom.label})`,
right.x - diameter,
bottom.y - diameter
)

// 左下
p.textAlign('left', 'bottom')
p.rect(left.x, bottom.y - diameter, diameter)
p.text(
`(${left.label}, ${bottom.label})`,
left.x + diameter,
bottom.y - diameter
)
}
}
45 changes: 45 additions & 0 deletions src/useCases/Home/components/Hero/assets/effect/debug/Point.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import p5 from 'p5'

export class Point {
#p: p5
x: number
y: number
theta: number

constructor(p: p5, { x, y, theta }: Record<'x' | 'y' | 'theta', number>) {
this.#p = p

this.x = x
this.y = y
this.theta = theta

p.fill('rgba(50, 150, 50, 0.95)')
}

circle(p: p5) {
p.strokeWeight(1)
p.circle(this.x, this.y, 8)
}

label(p: p5) {
p.noStroke()
p.textSize(8)
p.textFont('monospace')
p.textAlign('left', 'center')

p.text(
[
`θ=${this.theta}`,
`x=${p.round(this.x, 2)}`,
`y=${p.round(this.y, 2)}`
].join('\n'),
this.x + 12,
this.y
)
}

display() {
this.circle(this.#p)
this.label(this.#p)
}
}
124 changes: 124 additions & 0 deletions src/useCases/Home/components/Hero/assets/effect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import p5 from 'p5'
import { debounce } from '../utils'
import { Frame } from './debug/Frame.ts'
import { Point } from './debug/Point.ts'

type Options = {
selector: HTMLElement
colors: { line: string; background: string }
enableDebug?: boolean
}

export class Effect {
options: Required<Options>

constructor(options: Options) {
this.options = { enableDebug: false, ...options }
}

#createSketch(p: p5, { node }: { node: HTMLElement }) {
// VRTを考慮して同じパターンが出力されるようにSeed値を設定する
p.noiseSeed(100)

p.setup = () => {
const { colors } = this.options

p.createCanvas(node.offsetWidth, node.offsetHeight)
p.background(colors.background)

if (this.#disableAnimation) {
p.noLoop()
}
}

p.draw = (() => {
const { colors } = this.options
const step = p.TWO_PI * 3 * 0.1 ** 2
const speed = step * 0.1 ** 5
const offset = {
y: 0,
z: 0
}
const spacing = 5
const { drawEdgeCoord, drawPoint } = this.#debug(
p,
this.options.enableDebug
)

return () => {
p.clear()
p.translate(p.width, 0)

p.beginShape()
for (let theta = 0; theta < p.width; theta += step) {
// 対数螺旋を描く
const radian = p.pow(1.06, theta)
const noise = p.noise(theta, offset.y, offset.z) + 1

const x = (spacing + radian) * p.cos(theta) * noise
const y = (spacing + radian) * p.sin(theta) * noise

// 画面外の場合は描画を終了する
if (x < -p.width && p.height < y) break

p.curveVertex(x, y)
drawPoint({ x, y, theta })

offset.y -= 2 * speed
offset.z += 2 * speed
}

p.stroke(colors.line)
p.strokeWeight(1)
p.smooth()
p.noFill()
p.endShape()

drawEdgeCoord()
}
})()
}

#init() {
const { selector: node } = this.options
const p = new p5((p) => this.#createSketch(p, { node }), node)

return {
resize: () => p.resizeCanvas(node.offsetWidth, node.offsetHeight),
animate: (mode: 'stop' | 'start') => {
if (this.#disableAnimation) return
mode === 'stop' ? p.noLoop() : p.loop()
}
}
}

#debug(p: p5, enable: boolean) {
p.mouseClicked = enable
? () => {
enable = !enable
return false
}
: () => {}

return {
drawEdgeCoord: () => enable && new Frame(p).display(),
drawPoint: (options: ConstructorParameters<typeof Point>['1']) =>
enable && new Point(p, options).display()
}
}

get #disableAnimation() {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}

draw() {
const { resize, animate } = this.#init()

// ウィンドウがリサイズされたらエフェクトもリサイズする
window.addEventListener('resize', debounce(resize, 800))
// 画面外に表示された場合はアニメーションを止める
new IntersectionObserver((entries) =>
animate(entries[0].isIntersecting ? 'start' : 'stop')
).observe(this.options.selector)
}
}
17 changes: 17 additions & 0 deletions src/useCases/Home/components/Hero/assets/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const debounce = (callback: () => void, delaySec: number) => {
type Timer = ReturnType<Window['setTimeout']> | null
let timer: Timer = null

const clear = (timer: Timer) => {
if (timer) clearTimeout(timer)
timer = null
}

return () => {
clear(timer)
timer = window.setTimeout(() => {
callback()
clear(timer)
}, delaySec)
}
}

0 comments on commit bf01f0a

Please sign in to comment.