Skip to content

Commit 362f189

Browse files
author
takuma-hmng8
committed
update stickers
1 parent ff0f3a8 commit 362f189

File tree

49 files changed

+659
-415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+659
-415
lines changed

app/stickers/CanvasState.ts

+26-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as THREE from "three";
22

33
export class CanvasState {
44
private static instance: CanvasState;
5+
56
public static getInstance(): CanvasState {
67
if (!CanvasState.instance) {
78
CanvasState.instance = new CanvasState();
@@ -23,7 +24,13 @@ export class CanvasState {
2324
}
2425

2526
private getNextStickerIndex() {
26-
return Math.floor(Math.random() * this.textures.stickers.length);
27+
const prevIndex = this.stickerState.nextStickerIndex;
28+
let nextIndex;
29+
// 重複しないように次のステッカーを選ぶ
30+
do {
31+
nextIndex = Math.floor(Math.random() * this.textures.stickers.length);
32+
} while (nextIndex === prevIndex);
33+
return nextIndex;
2734
}
2835

2936
public textures: {
@@ -39,9 +46,16 @@ export class CanvasState {
3946
point: new THREE.Vector2(0, 0),
4047
};
4148

42-
public state: {
49+
public cameraState: {
50+
point: THREE.Vector3;
51+
} = {
52+
point: new THREE.Vector3(0, 0, 4),
53+
};
54+
55+
public stickerState: {
56+
isNotSticked: boolean;
57+
wobbleStrength: number;
4358
point: THREE.Vector2;
44-
cameraPoint: THREE.Vector3;
4559
sticker: THREE.Texture | null;
4660
nextStickerIndex: number;
4761
wrinkle: THREE.Texture | null;
@@ -50,26 +64,24 @@ export class CanvasState {
5064
angle: number;
5165
count: number;
5266
} = {
67+
isNotSticked: true,
68+
wobbleStrength: 0,
5369
point: new THREE.Vector2(0, 0),
54-
cameraPoint: new THREE.Vector3(0, 0, 4),
5570
sticker: null,
56-
nextStickerIndex: this.getNextStickerIndex(),
71+
nextStickerIndex: 0,
5772
wrinkle: null,
5873
wrinkleIntensity: Math.random(),
5974
size: this.getRandomSize(),
6075
angle: this.getRandomAngle(),
6176
count: 0,
6277
};
6378

64-
public setState(point: THREE.Vector2, cameraPoint: THREE.Vector3) {
65-
this.state = {
79+
public setStickerState(point: THREE.Vector2) {
80+
this.stickerState = {
81+
isNotSticked: false,
82+
wobbleStrength: 0.4,
6683
point: point,
67-
cameraPoint: this.state.cameraPoint.set(
68-
cameraPoint.x,
69-
cameraPoint.y,
70-
3.5
71-
),
72-
sticker: this.textures.stickers[this.state.nextStickerIndex],
84+
sticker: this.textures.stickers[this.stickerState.nextStickerIndex],
7385
nextStickerIndex: this.getNextStickerIndex(),
7486
wrinkle:
7587
this.textures.wrinkles[
@@ -78,7 +90,7 @@ export class CanvasState {
7890
wrinkleIntensity: Math.random(),
7991
size: this.getRandomSize(),
8092
angle: this.getRandomAngle(),
81-
count: this.state.count + 2,
93+
count: this.stickerState.count + 2,
8294
};
8395
}
8496
}

app/stickers/CursorUI/Confetti.tsx

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Image from "next/image";
2+
import { useEffect, useState } from "react";
3+
import s from "./index.module.scss";
4+
5+
export const Confetti = ({ state }: { state: number }) => {
6+
const [styles, setStyles] = useState<
7+
{ top: string; left: string; scale: string }[]
8+
>([]);
9+
10+
useEffect(() => {
11+
// ハイドレーションエラーを回避するために、must be useEffect
12+
const newStyles = [...Array(8)].map(() => ({
13+
top: `${Math.random() * 140 - 20}%`,
14+
left: `${Math.random() * 140 - 20}%`,
15+
scale: `${Math.random() + 0.5}`,
16+
}));
17+
setStyles(newStyles);
18+
}, [state]);
19+
20+
return (
21+
<>
22+
{styles.map((style, i) => (
23+
<div className={s.confetti} key={i} style={style}>
24+
<Image
25+
src="/stickers/gif/gif3.gif"
26+
fill
27+
alt=""
28+
unoptimized
29+
style={{ visibility: "hidden" }}
30+
/>
31+
</div>
32+
))}
33+
</>
34+
);
35+
};
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { memo } from "react";
2+
import Image from "next/image";
3+
import { STICKER_TEXTURES_LENGTH } from "../useStickers";
4+
5+
const GIF_IMAGES = [...Array(STICKER_TEXTURES_LENGTH)].map(
6+
(_, i) => `/stickers/gif/gif${i}.gif`
7+
);
8+
9+
/** 事前に全てのgif fileのloadを保証する */
10+
export const GifPreloader = memo(() => {
11+
return (
12+
<div
13+
style={{
14+
visibility: "hidden",
15+
opacity: 0,
16+
pointerEvents: "none",
17+
position: "fixed",
18+
width: 1,
19+
height: 1,
20+
}}>
21+
{GIF_IMAGES.map((src, i) => (
22+
<Image
23+
key={i}
24+
src={src}
25+
fill
26+
alt=""
27+
priority
28+
unoptimized
29+
style={{ visibility: "hidden" }}
30+
/>
31+
))}
32+
</div>
33+
);
34+
});
35+
36+
GifPreloader.displayName = "GifPreloader";

app/stickers/CursorUI/index.module.scss

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
}
2121
}
2222

23+
/*===============================================
24+
confetti
25+
===============================================*/
2326
.confettiContainer {
2427
position: absolute;
2528
top: 0;

app/stickers/CursorUI/index.tsx

+17-73
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import * as THREE from "three";
44
import Image from "next/image";
55
import { CanvasState } from "../CanvasState";
6-
import { useCallback, useEffect, useRef, useState, memo } from "react";
7-
import s from "./index.module.scss";
6+
import { useCallback, useEffect, useRef, useState } from "react";
87
import gsap from "gsap";
9-
import { STICKER_TEXTURES_LENGTH } from "../useStickers";
8+
import { GifPreloader } from "./GifPreloader";
9+
import { Confetti } from "./Confetti";
10+
import s from "./index.module.scss";
1011

1112
const updateTargetPoint = (target: HTMLDivElement, point: THREE.Vector2) => {
1213
const targetRect = target.getBoundingClientRect();
@@ -16,75 +17,11 @@ const updateTargetPoint = (target: HTMLDivElement, point: THREE.Vector2) => {
1617

1718
const CURSOR_LERP = 0.8;
1819

19-
const GIF_IMAGES = [...Array(STICKER_TEXTURES_LENGTH)].map(
20-
(_, i) => `/stickers/gif/gif${i}.gif`
21-
);
22-
23-
const GifPreloader = memo(() => {
24-
console.log("render");
25-
return (
26-
<div
27-
style={{
28-
visibility: "hidden",
29-
opacity: 0,
30-
pointerEvents: "none",
31-
position: "fixed",
32-
width: 1,
33-
height: 1,
34-
}}>
35-
{GIF_IMAGES.map((src, i) => (
36-
<Image
37-
key={i}
38-
src={src}
39-
fill
40-
alt=""
41-
priority
42-
unoptimized
43-
style={{ visibility: "hidden" }}
44-
/>
45-
))}
46-
</div>
47-
);
48-
});
49-
50-
GifPreloader.displayName = "GifPreloader";
51-
52-
const Confetti = ({ state }: { state: number }) => {
53-
const [styles, setStyles] = useState<
54-
{ top: string; left: string; scale: string }[]
55-
>([]);
56-
57-
useEffect(() => {
58-
// ハイドレーションエラーを回避するために、must be useEffect
59-
const newStyles = [...Array(8)].map(() => ({
60-
top: `${Math.random() * 140 - 20}%`,
61-
left: `${Math.random() * 140 - 20}%`,
62-
scale: `${Math.random() + 0.5}`,
63-
}));
64-
setStyles(newStyles);
65-
}, [state]);
66-
67-
return (
68-
<>
69-
{styles.map((style, i) => (
70-
<div className={s.confetti} key={i} style={style}>
71-
<Image
72-
src="/stickers/gif/gif3.gif"
73-
fill
74-
alt=""
75-
unoptimized
76-
style={{ visibility: "hidden" }}
77-
/>
78-
</div>
79-
))}
80-
</>
81-
);
82-
};
83-
8420
export const CursorUI = () => {
8521
const canvasState = CanvasState.getInstance();
22+
8623
const [stickerIndex, setStickerIndex] = useState(
87-
canvasState.state.nextStickerIndex
24+
canvasState.stickerState.nextStickerIndex
8825
);
8926

9027
const rafID = useRef<number>(0);
@@ -100,7 +37,7 @@ export const CursorUI = () => {
10037
confettiTarget: HTMLDivElement,
10138
point: THREE.Vector2
10239
) => {
103-
setStickerIndex(canvasState.state.nextStickerIndex);
40+
setStickerIndex(canvasState.stickerState.nextStickerIndex);
10441
gsap.fromTo(
10542
target,
10643
{
@@ -146,7 +83,7 @@ export const CursorUI = () => {
14683
}
14784
);
14885
},
149-
[canvasState]
86+
[canvasState.stickerState]
15087
);
15188

15289
const onOver = useCallback((target: HTMLImageElement) => {
@@ -182,7 +119,7 @@ export const CursorUI = () => {
182119
const targetImage = imageRef.current!;
183120

184121
// update sticker index
185-
if (stickerIndex !== canvasState.state.nextStickerIndex) {
122+
if (stickerIndex !== canvasState.stickerState.nextStickerIndex) {
186123
onStickerChange(
187124
targetImage,
188125
confettiRef.current!,
@@ -212,7 +149,14 @@ export const CursorUI = () => {
212149
}
213150
prevIsOver.current = isOver;
214151
rafID.current = requestAnimationFrame(handleFrame);
215-
}, [canvasState, stickerIndex, onStickerChange, onOver, onOut]);
152+
}, [
153+
canvasState.stickerState,
154+
stickerIndex,
155+
canvasState.cursorState,
156+
onStickerChange,
157+
onOver,
158+
onOut,
159+
]);
216160

217161
useEffect(() => {
218162
rafID.current = requestAnimationFrame(handleFrame);

0 commit comments

Comments
 (0)