From b866ec3dc0f33539de3865b9124532958b61b4a5 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 12 Jan 2024 13:00:37 +0100 Subject: [PATCH] Emoji Background: Render on canvas (#4151) --- .../common/embedded/EmbeddedMessage.scss | 8 +- .../embedded/EmojiIconBackground.module.scss | 8 +- .../common/embedded/EmojiIconBackground.tsx | 162 +++++++++++++----- src/components/middle/message/WebPage.scss | 21 +-- src/components/middle/message/WebPage.tsx | 2 +- 5 files changed, 133 insertions(+), 68 deletions(-) diff --git a/src/components/common/embedded/EmbeddedMessage.scss b/src/components/common/embedded/EmbeddedMessage.scss index 5aa66967f..97b6c1ba5 100644 --- a/src/components/common/embedded/EmbeddedMessage.scss +++ b/src/components/common/embedded/EmbeddedMessage.scss @@ -24,6 +24,10 @@ } } + &--background-icons { + margin: -0.1875rem -0.375rem -0.1875rem -0.1875rem; + } + .custom-shape & { max-width: 15rem; margin: 0; @@ -183,10 +187,6 @@ object-fit: cover; } - &--background-icons { - color: var(--accent-color); - } - &.inside-input { flex-grow: 1; margin: 0; diff --git a/src/components/common/embedded/EmojiIconBackground.module.scss b/src/components/common/embedded/EmojiIconBackground.module.scss index 795b588bf..bd622d6ec 100644 --- a/src/components/common/embedded/EmojiIconBackground.module.scss +++ b/src/components/common/embedded/EmojiIconBackground.module.scss @@ -1,7 +1,5 @@ .root { - --custom-emoji-border-radius: 0.25rem; - --custom-emoji-size: 1.25rem; - + color: var(--accent-color); position: absolute; top: 0; right: 0; @@ -9,7 +7,3 @@ left: 0; pointer-events: none; } - -.emoji { - position: absolute; -} diff --git a/src/components/common/embedded/EmojiIconBackground.tsx b/src/components/common/embedded/EmojiIconBackground.tsx index 27be167fe..6ee00a67e 100644 --- a/src/components/common/embedded/EmojiIconBackground.tsx +++ b/src/components/common/embedded/EmojiIconBackground.tsx @@ -1,9 +1,20 @@ -import React, { memo } from '../../../lib/teact/teact'; +import React, { + memo, useEffect, useRef, useState, +} from '../../../lib/teact/teact'; +import { requestMutation } from '../../../lib/fasterdom/fasterdom'; +import { getStickerPreviewHash } from '../../../global/helpers'; import buildClassName from '../../../util/buildClassName'; -import buildStyle from '../../../util/buildStyle'; +import { preloadImage } from '../../../util/files'; +import { REM } from '../helpers/mediaDimensions'; -import CustomEmoji from '../CustomEmoji'; +import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useMedia from '../../../hooks/useMedia'; +import useResizeObserver from '../../../hooks/useResizeObserver'; +import useDevicePixelRatio from '../../../hooks/window/useDevicePixelRatio'; +import useCustomEmoji from '../hooks/useCustomEmoji'; import styles from './EmojiIconBackground.module.scss'; @@ -16,37 +27,35 @@ type IconPosition = { const ICON_POSITIONS: IconPosition[] = [ { - inline: 5, block: 15, opacity: 0.35, scale: 1, + inline: 22, block: 38, opacity: 0.35, scale: 0.75, }, { - inline: 10, block: 45, opacity: 0.3, scale: 0.9, + inline: 32, block: 12, opacity: 0.3, scale: 1, }, { - inline: 20, block: 75, opacity: 0.3, scale: 0.75, + inline: 60, block: 22, opacity: 0.25, scale: 0.75, }, { - inline: 40, block: 20, opacity: 0.25, scale: 0.8, + inline: 75, block: 44, opacity: 0.25, scale: 1, }, { - inline: 60, block: 50, opacity: 0.25, scale: 0.85, + inline: 75, block: 2, opacity: 0.2, scale: 0.625, }, { - inline: 55, block: -5, opacity: 0.20, scale: 0.75, + inline: 95, block: 18, opacity: 0.2, scale: 1, }, { - inline: 80, block: 15, opacity: 0.15, scale: 0.95, + inline: 115, block: 38, opacity: 0.2, scale: 0.625, }, { - inline: 100, block: 70, opacity: 0.15, scale: 0.9, - }, - { - inline: 120, block: 25, opacity: 0.10, scale: 0.65, - }, - { - inline: 140, block: 0, opacity: 0.10, scale: 0.75, + inline: 125, block: 12, opacity: 0.1, scale: 0.75, }, ]; +const EMOJI_SIZE = REM; +const LOTTIE_TINT_OPACITY = 'ff'; +const NON_LOTTIE_TINT_OPACITY = 'bb'; + type OwnProps = { emojiDocumentId: string; className?: string; @@ -56,29 +65,104 @@ const EmojiIconBackground = ({ emojiDocumentId, className, }: OwnProps) => { + // eslint-disable-next-line no-null/no-null + const canvasRef = useRef(null); + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + + const [emojiImage, setEmojiImage] = useState(); + + const dpr = useDevicePixelRatio(); + + const lang = useLang(); + + const { customEmoji } = useCustomEmoji(emojiDocumentId); + const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined; + const previewUrl = useMedia(previewMediaHash); + + const customColor = useDynamicColorListener(containerRef); + + useEffect(() => { + if (!previewUrl) return; + + preloadImage(previewUrl).then(setEmojiImage); + }, [previewUrl]); + + const updateCanvas = useLastCallback(() => { + const canvas = canvasRef.current; + if (!canvas || !emojiImage || !customColor) return; + + const context = canvas.getContext('2d')!; + const { width, height } = canvas; + + context.clearRect(0, 0, width, height); + + ICON_POSITIONS.forEach(({ + inline, block, opacity, scale, + }) => { + const x = (lang.isRtl ? inline : width / dpr - inline) * dpr; + const y = block * dpr; + + const emojiSize = EMOJI_SIZE * dpr; + + context.save(); + context.globalAlpha = opacity; + context.translate(x, y); + context.scale(scale, scale); + context.drawImage(emojiImage, -emojiSize / 2, -emojiSize / 2, emojiSize, emojiSize); + context.restore(); + }); + + const tintColor = `${customColor}${customEmoji!.isLottie ? LOTTIE_TINT_OPACITY : NON_LOTTIE_TINT_OPACITY}`; + + context.save(); + context.fillStyle = tintColor; + context.globalCompositeOperation = 'source-atop'; + context.fillRect(0, 0, width, height); + context.restore(); + }); + + useEffect(() => { + updateCanvas(); + }, [emojiImage, lang.isRtl, customColor]); + + const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => { + const canvas = canvasRef.current; + if (!canvas) return; + + canvas.width = parentWidth * dpr; + canvas.height = parentHeight * dpr; + + canvas.style.width = `${parentWidth}px`; + canvas.style.height = `${parentHeight}px`; + + updateCanvas(); + }); + + const handleResize = useLastCallback((entry: ResizeObserverEntry) => { + const { width, height } = entry.contentRect; + + requestMutation(() => { + updateCanvasSize(width, height); + }); + }); + + useResizeObserver(containerRef, handleResize); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const { width, height } = container.getBoundingClientRect(); + + requestMutation(() => { + updateCanvasSize(width, height); + }); + }, [dpr]); + return ( -
- {ICON_POSITIONS.map((position) => { - const { - inline, block, opacity, scale, - } = position; - - const style = buildStyle( - `inset-inline-end: ${inline}px`, - `inset-block-start: ${block}px`, - `opacity: ${opacity}`, - `transform: scale(${scale})`, - ); - - return ( - - ); - })} +
+
); }; diff --git a/src/components/middle/message/WebPage.scss b/src/components/middle/message/WebPage.scss index 0cd76b077..08a1c1d41 100644 --- a/src/components/middle/message/WebPage.scss +++ b/src/components/middle/message/WebPage.scss @@ -10,6 +10,10 @@ position: relative; overflow: hidden; + &--background-icons { + margin: -0.375rem; + } + &.in-preview { border-radius: 0.25rem; background-color: var(--color-primary-tint); @@ -59,10 +63,6 @@ } } - &--background-icons { - color: var(--accent-color); - } - &-text { display: flex; flex-direction: column; @@ -137,19 +137,6 @@ } } - &:not(.with-square-photo):not(.with-quick-button) { - .site-name, - .site-title, - .site-description { - &:last-child::after { - content: ""; - width: var(--meta-safe-area-size); - height: 0.75rem; - float: right; - } - } - } - .site-name, .site-description, .site-title { diff --git a/src/components/middle/message/WebPage.tsx b/src/components/middle/message/WebPage.tsx index b147d5309..4d60ae946 100644 --- a/src/components/middle/message/WebPage.tsx +++ b/src/components/middle/message/WebPage.tsx @@ -140,7 +140,7 @@ const WebPage: FC = ({
{isStory && (