Skip to content

Commit 595d2f4

Browse files
committed
Radial Pattern Background: Various improvements
1 parent 5e67921 commit 595d2f4

File tree

6 files changed

+126
-72
lines changed

6 files changed

+126
-72
lines changed

src/components/common/gift/SavedGift.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ const SavedGift = ({
140140
backgroundColors={backdropColors}
141141
patternColor={patternColor}
142142
patternIcon={pattern.sticker}
143+
patternSize={14}
144+
ringsCount={1}
145+
ovalFactor={1}
143146
/>
144147
);
145148
}, [backdrop, pattern]);

src/components/common/profile/ProfileInfo.module.scss

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,14 +386,9 @@
386386

387387
.radialPatternBackground {
388388
pointer-events: none;
389-
390-
// Hacky way to keep background stable during resizes
391389
position: absolute;
392-
top: 8rem;
393-
390+
inset: 0;
394391
aspect-ratio: 1 / 1;
395-
width: 140%;
396-
margin-top: -70%;
397392
}
398393

399394
.standaloneAvatar {

src/components/common/profile/ProfileInfo.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import buildStyle from '../../../util/buildStyle';
4545
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
4646
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
4747
import { resolveTransitionName } from '../../../util/resolveTransitionName';
48+
import { REM } from '../helpers/mediaDimensions.ts';
4849
import renderText from '../helpers/renderText.tsx';
4950

5051
import { useVtn } from '../../../hooks/animations/useVtn';
@@ -104,9 +105,7 @@ const EMOJI_TOPIC_SIZE = 120;
104105
const LOAD_MORE_THRESHOLD = 3;
105106
const MAX_PHOTO_DASH_COUNT = 30;
106107
const STATUS_UPDATE_INTERVAL = 1000 * 60; // 1 min
107-
const PATTERN_COLOR = '#000000';
108-
const PATTERN_SIZE_FACTOR = 0.75;
109-
const PATTERN_OPACITY = 0.75;
108+
const PATTERN_Y_SHIFT = 8 * REM;
110109

111110
const ProfileInfo = ({
112111
isExpanded,
@@ -493,10 +492,11 @@ const ProfileInfo = ({
493492
<RadialPatternBackground
494493
backgroundColors={profileColorSet.bgColors}
495494
patternIcon={backgroundEmoji}
496-
patternColor={collectibleEmojiStatus?.patternColor || PATTERN_COLOR}
495+
patternColor={collectibleEmojiStatus?.patternColor}
496+
patternSize={16}
497+
withLinearGradient={Boolean(!collectibleEmojiStatus && profileColorSet.bgColors[1])}
497498
className={styles.radialPatternBackground}
498-
patternSize={PATTERN_SIZE_FACTOR}
499-
patternOpacity={collectibleEmojiStatus ? 1 : PATTERN_OPACITY}
499+
yPosition={PATTERN_Y_SHIFT}
500500
/>
501501
)}
502502
{pinnedGifts && (

src/components/common/profile/RadialPatternBackground.module.scss

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.root {
2+
--_y-shift: 50%;
3+
24
overflow: hidden;
35
border-radius: inherit;
46

@@ -9,9 +11,15 @@
911
position: absolute;
1012
inset: 0;
1113

12-
background-image:
13-
radial-gradient(circle closest-side, #ffffff32 3rem, #ffffff00 7rem),
14-
radial-gradient(closest-side, var(--_bg-1), var(--_bg-2));
14+
background-image: radial-gradient(closest-side at 50% var(--_y-shift), var(--_bg-radial-1) 25%, var(--_bg-radial-2) 125%);
15+
}
16+
17+
&.withLinearGradient {
18+
&::before {
19+
background-image:
20+
radial-gradient(closest-side at 50% var(--_y-shift), var(--_bg-radial-1) 25%, var(--_bg-radial-2) 125%),
21+
linear-gradient(var(--_bg-linear-1), var(--_bg-linear-2));
22+
}
1523
}
1624
}
1725

Lines changed: 102 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {
2-
memo, useEffect, useMemo, useRef, useSignal, useState,
3-
} from '../../../lib/teact/teact';
1+
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useSignal, useState } from '../../../lib/teact/teact';
42

53
import type { ApiSticker } from '../../../api/types';
64

@@ -9,7 +7,8 @@ import { getStickerMediaHash } from '../../../global/helpers';
97
import buildClassName from '../../../util/buildClassName';
108
import buildStyle from '../../../util/buildStyle';
119
import { preloadImage } from '../../../util/files';
12-
import { clamp } from '../../../util/math';
10+
import { hexToRgb } from '../../../util/switchTheme.ts';
11+
import { REM } from '../helpers/mediaDimensions';
1312

1413
import useLastCallback from '../../../hooks/useLastCallback';
1514
import useMedia from '../../../hooks/useMedia';
@@ -20,31 +19,39 @@ import styles from './RadialPatternBackground.module.scss';
2019

2120
type OwnProps = {
2221
backgroundColors: string[];
23-
patternColor?: string;
2422
patternIcon?: ApiSticker;
23+
patternColor?: string;
24+
patternSize?: number;
25+
ringsCount?: number;
26+
ovalFactor?: number;
27+
withLinearGradient?: boolean;
2528
className?: string;
2629
clearBottomSector?: boolean;
27-
patternSize?: number;
28-
patternOpacity?: number;
30+
yPosition?: number;
2931
};
3032

31-
const RINGS = 3;
3233
const BASE_RING_ITEM_COUNT = 8;
3334
const RING_INCREMENT = 0.5;
34-
const CENTER_EMPTINESS = 0.05;
35-
const MAX_RADIUS = 0.4;
36-
const BASE_ICON_SIZE = 20;
35+
const CENTER_EMPTINESS = 0.1;
36+
const MAX_RADIUS = 0.43;
37+
const MIN_SIZE = 4 * REM;
38+
const PATTERN_OPACITY = 0.9;
3739

38-
const MIN_SIZE = 250;
40+
const DEFAULT_PATTERN_SIZE = 20;
41+
const DEFAULT_RINGS_COUNT = 3;
42+
const DEFAULT_OVAL_FACTOR = 1.4;
3943

4044
const RadialPatternBackground = ({
4145
backgroundColors,
42-
patternColor,
4346
patternIcon,
44-
patternOpacity,
47+
patternColor,
48+
patternSize = DEFAULT_PATTERN_SIZE,
49+
ringsCount = DEFAULT_RINGS_COUNT,
50+
ovalFactor = DEFAULT_OVAL_FACTOR,
51+
withLinearGradient,
4552
clearBottomSector,
4653
className,
47-
patternSize = 1,
54+
yPosition,
4855
}: OwnProps) => {
4956
const containerRef = useRef<HTMLDivElement>();
5057
const canvasRef = useRef<HTMLCanvasElement>();
@@ -65,11 +72,10 @@ const RadialPatternBackground = ({
6572

6673
const patternPositions = useMemo(() => {
6774
const coordinates: { x: number; y: number; sizeFactor: number }[] = [];
68-
for (let ring = 1; ring <= RINGS; ring++) {
75+
for (let ring = 1; ring <= ringsCount; ring++) {
6976
const ringItemCount = Math.floor(BASE_RING_ITEM_COUNT * (1 + (ring - 1) * RING_INCREMENT));
70-
const ringProgress = ring / RINGS;
77+
const ringProgress = ring / ringsCount;
7178
const ringRadius = CENTER_EMPTINESS + (MAX_RADIUS - CENTER_EMPTINESS) * ringProgress;
72-
7379
const angleShift = ring % 2 === 0 ? Math.PI / ringItemCount : 0;
7480

7581
for (let i = 0; i < ringItemCount; i++) {
@@ -79,11 +85,9 @@ const RadialPatternBackground = ({
7985
continue;
8086
}
8187

82-
// Slightly oval
83-
const xOffset = ringRadius * 1.71 * Math.cos(angle);
88+
const xOffset = ringRadius * Math.cos(angle) * ovalFactor;
8489
const yOffset = ringRadius * Math.sin(angle);
85-
86-
const sizeFactor = 1.4 - ringProgress * Math.random();
90+
const sizeFactor = 1.65 - ringProgress + Math.random() / ringsCount;
8791

8892
coordinates.push({
8993
x: xOffset,
@@ -93,7 +97,7 @@ const RadialPatternBackground = ({
9397
}
9498
}
9599
return coordinates;
96-
}, [clearBottomSector]);
100+
}, [clearBottomSector, ovalFactor, ringsCount]);
97101

98102
useResizeObserver(containerRef, (entry) => {
99103
setContainerSize({
@@ -119,59 +123,67 @@ const RadialPatternBackground = ({
119123
const { width, height } = canvas;
120124
if (!width || !height) return;
121125

126+
const centerX = width / 2;
127+
const centerY = yPosition !== undefined ? yPosition * dpr : height / 2;
128+
122129
ctx.clearRect(0, 0, width, height);
123130

124-
ctx.save();
131+
const patternImage = drawPattern(
132+
width, height, centerX, centerY, backgroundColors[1] ?? backgroundColors[0],
133+
);
134+
ctx.drawImage(patternImage, 0, 0, width, height);
135+
136+
const radialGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, width / 2);
137+
radialGradient.addColorStop(0.75, 'rgb(255 255 255 / 0)');
138+
radialGradient.addColorStop(1, 'rgb(255 255 255 / 0.75)');
139+
140+
// Alpha mask
141+
ctx.globalCompositeOperation = 'destination-out';
142+
ctx.fillStyle = radialGradient;
143+
ctx.fillRect(0, 0, width, height);
144+
});
145+
146+
const drawPattern = useLastCallback((
147+
width: number, height: number, centerX: number, centerY: number, color: string,
148+
) => {
149+
const canvas = new OffscreenCanvas(width, height);
150+
const ctx = canvas.getContext('2d')!;
151+
152+
ctx.globalAlpha = PATTERN_OPACITY;
153+
125154
patternPositions.forEach(({
126155
x, y, sizeFactor,
127156
}) => {
128-
const renderX = x * patternSize * Math.max(width, MIN_SIZE * dpr) + width / 2;
129-
const renderY = y * patternSize * Math.max(height, MIN_SIZE * dpr) + height / 2;
157+
const renderX = x * Math.max(width, MIN_SIZE * dpr) + centerX;
158+
const renderY = yPosition !== undefined ? y * Math.max(width, MIN_SIZE * dpr) + centerY
159+
: y * Math.max(height, MIN_SIZE * dpr) + centerY;
130160

131-
const size = BASE_ICON_SIZE * dpr * patternSize * sizeFactor;
161+
const size = patternSize * dpr * sizeFactor;
132162

133-
ctx.drawImage(emojiImage, renderX - size / 2, renderY - size / 2, size, size);
163+
ctx.drawImage(emojiImage!, renderX - size / 2, renderY - size / 2, size, size);
134164
});
135-
ctx.restore();
136-
137-
if (patternColor) {
138-
ctx.save();
139-
ctx.fillStyle = patternColor;
140-
ctx.globalCompositeOperation = 'source-atop';
141-
ctx.fillRect(0, 0, width, height);
142-
ctx.restore();
143-
}
144-
145-
const radialGradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width / 2);
146-
147-
const alpha = clamp(0.6 * (patternOpacity ?? 1), 0, 1);
148165

149-
radialGradient.addColorStop(0, `rgb(255 255 255 / ${1 - alpha})`);
150-
radialGradient.addColorStop(1, `rgb(255 255 255 / 1)`);
151-
152-
// Alpha mask
153-
ctx.save();
154-
ctx.globalCompositeOperation = 'destination-out';
155-
ctx.fillStyle = radialGradient;
166+
ctx.fillStyle = adjustBrightness(color, -0.075);
167+
ctx.globalCompositeOperation = 'source-in';
156168
ctx.fillRect(0, 0, width, height);
157-
ctx.restore();
169+
170+
return canvas;
158171
});
159172

160173
useEffect(() => {
161174
draw();
162-
}, [emojiImage, patternOpacity, patternSize, patternColor, patternPositions]);
175+
}, [emojiImage, patternColor, patternPositions, yPosition]);
163176

164-
useEffect(() => {
177+
useLayoutEffect(() => {
165178
const { width, height } = getContainerSize();
166179
const canvas = canvasRef.current;
167180
if (!width || !height || !canvas) {
168181
return;
169182
}
170183

171-
const maxSide = Math.max(width, height);
172184
requestMutation(() => {
173-
canvas.width = maxSide * dpr;
174-
canvas.height = maxSide * dpr;
185+
canvas.width = width * dpr;
186+
canvas.height = height * dpr;
175187

176188
draw();
177189
});
@@ -180,10 +192,10 @@ const RadialPatternBackground = ({
180192
return (
181193
<div
182194
ref={containerRef}
183-
className={buildClassName(styles.root, className)}
195+
className={buildClassName(styles.root, withLinearGradient && styles.withLinearGradient, className)}
184196
style={buildStyle(
185-
`--_bg-1: ${backgroundColors[0]}`,
186-
`--_bg-2: ${backgroundColors[1] || backgroundColors[0]}`,
197+
...buildGradients(backgroundColors, withLinearGradient),
198+
yPosition !== undefined && `--_y-shift: ${yPosition}px`,
187199
)}
188200
>
189201
<canvas
@@ -196,3 +208,36 @@ const RadialPatternBackground = ({
196208
};
197209

198210
export default memo(RadialPatternBackground);
211+
212+
function buildGradients(colors: string[], withLinearGradient = false) {
213+
if (withLinearGradient) {
214+
return [
215+
`--_bg-linear-1: ${colors[0]}`,
216+
`--_bg-linear-2: ${colors[1]}`,
217+
`--_bg-radial-1: #ffffff33`,
218+
`--_bg-radial-2: #ffffff00`,
219+
];
220+
}
221+
222+
return [
223+
`--_bg-radial-1: ${colors[1] ? colors[0] : adjustBrightness(colors[0], 0.2)}`,
224+
`--_bg-radial-2: ${colors[1] ?? colors[0]}`,
225+
];
226+
}
227+
228+
function adjustBrightness(hex: string, delta: number) {
229+
const factor = 1 + delta;
230+
const rgba = hexToRgb(hex);
231+
const darkenedRgba = [
232+
Math.min(255, Math.round(rgba.r * factor)),
233+
Math.min(Math.round(rgba.g * factor)),
234+
Math.min(Math.round(rgba.b * factor)),
235+
rgba.a ?? 1,
236+
] as const;
237+
238+
return rgbaToHex(...darkenedRgba);
239+
}
240+
241+
function rgbaToHex(r: number, g: number, b: number, a: number) {
242+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}${Math.round(a * 255).toString(16)}`;
243+
}

src/components/middle/message/actions/StarGiftUnique.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment.ts';
1515
import buildClassName from '../../../../util/buildClassName';
1616
import buildStyle from '../../../../util/buildStyle';
1717
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
18+
import { REM } from '../../../common/helpers/mediaDimensions.ts';
1819
import { renderPeerLink } from '../helpers/messageActions';
1920

2021
import useFlag from '../../../../hooks/useFlag.ts';
@@ -98,6 +99,8 @@ const StarGiftAction = ({
9899
backgroundColors={backgroundColors}
99100
patternColor={backdrop.patternColor}
100101
patternIcon={pattern.sticker}
102+
patternSize={14}
103+
yPosition={9.5 * REM}
101104
clearBottomSector
102105
/>
103106
</div>

0 commit comments

Comments
 (0)