Skip to content

Commit 4eeddfc

Browse files
puroongcpojer
andauthored
perf: draw portrait with canvas (#32)
Co-authored-by: cpojer <[email protected]>
1 parent 803185b commit 4eeddfc

File tree

2 files changed

+58
-50
lines changed

2 files changed

+58
-50
lines changed

art/VariantConfiguration.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default new Map<SpriteVariant, SpriteVariantConfiguration>([
6565
[
6666
'Portraits',
6767
{
68+
asImage: true,
6869
variantNames: new Set<PlainDynamicPlayerID>([
6970
...variantNames,
7071
-1,

hera/character/Portrait.tsx

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { spriteURL } from '@deities/art/Sprites.tsx';
1+
import { spriteImage } from '@deities/art/Sprites.tsx';
22
import { UnitInfo } from '@deities/athena/info/Unit.tsx';
33
import {
44
DynamicPlayerID,
55
encodeDynamicPlayerID,
66
} from '@deities/athena/map/Player.tsx';
77
import clipBorder from '@deities/ui/clipBorder.tsx';
8-
import { CSSVariables } from '@deities/ui/cssVar.tsx';
9-
import { css, cx, keyframes } from '@emotion/css';
10-
import { memo } from 'react';
8+
import { css, cx } from '@emotion/css';
9+
import { memo, useLayoutEffect, useRef } from 'react';
1110
import { useSprites } from '../hooks/useSprites.tsx';
1211

1312
export const PortraitWidth = 55;
@@ -30,68 +29,76 @@ export default memo(function Portrait({
3029
unit: UnitInfo;
3130
variant?: number;
3231
}) {
32+
const ref = useRef<HTMLCanvasElement>(null);
3333
const hasPortraits = useSprites('portraits');
34-
const sprite = hasPortraits
35-
? spriteURL('Portraits', encodeDynamicPlayerID(player))
36-
: '';
37-
3834
const { position } = unit.sprite.portrait;
39-
const y = -(position.y + (variant || 0)) * PortraitHeight + 'px';
40-
const alternateY = -(position.y + (variant || 0) + 6) * PortraitHeight + 'px';
35+
36+
useLayoutEffect(() => {
37+
if (!hasPortraits) {
38+
return;
39+
}
40+
41+
const canvas = ref.current;
42+
if (!canvas) {
43+
return;
44+
}
45+
46+
const image = spriteImage('Portraits', encodeDynamicPlayerID(player));
47+
const context = canvas.getContext('2d')!;
48+
const positions = [
49+
{
50+
x: position.x * PortraitWidth,
51+
y: (position.y + (variant || 0)) * PortraitHeight,
52+
},
53+
{
54+
x: position.x * PortraitWidth,
55+
y: (position.y + (variant || 0) + 6) * PortraitHeight,
56+
},
57+
];
58+
let currentPosition =
59+
((Number(document.timeline.currentTime) || 0) / 1000) % 1 < 0.5 ? 0 : 1;
60+
61+
const draw = () => {
62+
context.drawImage(
63+
image,
64+
positions[currentPosition].x,
65+
positions[currentPosition].y,
66+
PortraitWidth,
67+
PortraitHeight,
68+
0,
69+
0,
70+
PortraitWidth,
71+
PortraitHeight,
72+
);
73+
currentPosition = (currentPosition + 1) % positions.length;
74+
};
75+
76+
draw();
77+
78+
if (animate && !paused) {
79+
const interval = setInterval(draw, 1000 / positions.length);
80+
return () => clearInterval(interval);
81+
}
82+
}, [hasPortraits, animate, paused, player, position, variant]);
4183

4284
return (
43-
<div
85+
<canvas
4486
className={cx(portraitStyle, clip && clipStyle)}
87+
height={PortraitHeight}
88+
ref={ref}
4589
style={{
46-
[vars.set('x')]: -position.x * PortraitWidth + 'px',
47-
[vars.set('y')]: y,
48-
[vars.set('alternate-y')]: alternateY,
49-
height: PortraitHeight,
50-
width: PortraitWidth,
5190
zoom: scale,
5291
}}
53-
>
54-
{sprite && (
55-
<img
56-
className={cx(
57-
spriteStyle,
58-
animate && animateStyle,
59-
paused && pausedStyle,
60-
)}
61-
src={sprite}
62-
/>
63-
)}
64-
</div>
92+
width={PortraitWidth}
93+
/>
6594
);
6695
});
6796

68-
const vars = new CSSVariables<'x' | 'y' | 'alternate-y'>('p');
69-
7097
const portraitStyle = css`
71-
contain: content;
7298
image-rendering: pixelated;
7399
pointer-events: none;
74100
`;
75101

76102
const clipStyle = css`
77103
${clipBorder(2)}
78104
`;
79-
80-
const spriteStyle = css`
81-
transform: translate3d(${vars.apply('x')}, ${vars.apply('y')}, 0);
82-
`;
83-
84-
const animateStyle = css`
85-
animation: ${keyframes`
86-
0%, 100% {
87-
transform: translate3d(${vars.apply('x')}, ${vars.apply('y')}, 0);
88-
}
89-
50% {
90-
transform: translate3d(${vars.apply('x')}, ${vars.apply('alternate-y')}, 0);
91-
}
92-
`} 1s infinite steps(1);
93-
`;
94-
95-
const pausedStyle = css`
96-
animation-play-state: paused;
97-
`;

0 commit comments

Comments
 (0)