Properly handle shared canvas resize

This commit is contained in:
Alexander Zinchuk 2022-11-13 17:06:12 +04:00
parent ee73e1ba73
commit dfe7b80a6b
4 changed files with 94 additions and 22 deletions

View File

@ -209,11 +209,17 @@ const AnimatedSticker: FC<OwnProps> = ({
}, [unfreezeAnimation]);
useOnChange(([prevNoLoop]) => {
if (noLoop !== undefined && noLoop !== prevNoLoop) {
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {
animation?.setNoLoop(noLoop);
}
}, [noLoop, animation]);
useOnChange(([prevSharedCanvasCoords]) => {
if (prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords) {
animation?.setSharedCanvasCoords(containerId, sharedCanvasCoords);
}
}, [sharedCanvasCoords, containerId, animation]);
useEffect(() => {
if (!animation) {
return;

View File

@ -20,6 +20,7 @@
.thumb {
width: 100%;
height: 100%;
pointer-events: none;
}
.media {
@ -42,7 +43,6 @@
left: 0;
width: 100%;
height: 100%;
z-index: 1;
user-select: auto !important;
}

View File

@ -1,4 +1,8 @@
import { useEffect, useMemo, useState } from '../lib/teact/teact';
import {
useCallback, useEffect, useMemo, useState,
} from '../lib/teact/teact';
import { throttle } from '../util/schedulers';
import { round } from '../util/math';
export default function useSharedCanvasCoords(
containerRef: React.RefObject<HTMLDivElement>,
@ -7,20 +11,42 @@ export default function useSharedCanvasCoords(
const [x, setX] = useState<number>();
const [y, setY] = useState<number>();
useEffect(() => {
if (!sharedCanvasRef?.current) {
const recalculate = useCallback(() => {
const container = containerRef.current;
const canvas = sharedCanvasRef?.current;
if (!container || !canvas) {
return;
}
const container = containerRef.current!;
const target = container.classList.contains('sticker-set-cover') ? container : container.querySelector('img')!;
const targetBounds = target.getBoundingClientRect();
const canvasBounds = sharedCanvasRef!.current!.getBoundingClientRect();
const canvasBounds = canvas.getBoundingClientRect();
// Factor coords are used to support rendering while being rescaled (e.g. message appearance animation)
setX((targetBounds.left - canvasBounds.left) / canvasBounds.width);
setY((targetBounds.top - canvasBounds.top) / canvasBounds.height);
setX(round((targetBounds.left - canvasBounds.left) / canvasBounds.width, 4));
setY(round((targetBounds.top - canvasBounds.top) / canvasBounds.height, 4));
}, [containerRef, sharedCanvasRef]);
useEffect(() => {
if (!('ResizeObserver' in window) || !sharedCanvasRef?.current) {
return undefined;
}
const observer = new ResizeObserver(throttle(([entry]) => {
// During animation
if (!(entry.target as HTMLCanvasElement).offsetParent) {
return;
}
recalculate();
}, 300, false));
observer.observe(sharedCanvasRef.current);
return () => {
observer.disconnect();
};
}, [recalculate, sharedCanvasRef]);
return useMemo(() => (x !== undefined && y !== undefined ? { x, y } : undefined), [x, y]);
}

View File

@ -7,6 +7,7 @@ import {
import WorkerConnector from '../../util/WorkerConnector';
import { animate } from '../../util/animation';
import cycleRestrict from '../../util/cycleRestrict';
import { fastRaf } from '../../util/schedulers';
interface Params {
noLoop?: boolean;
@ -120,9 +121,14 @@ class RLottie {
}
public removeContainer(containerId: string) {
const containerData = this.containers.get(containerId)!;
if (!containerData.isSharedCanvas) {
this.containers.get(containerId)!.canvas.remove();
const {
canvas, ctx, isSharedCanvas, coords,
} = this.containers.get(containerId)!;
if (isSharedCanvas) {
ctx.clearRect(coords!.x, coords!.y, this.imgSize, this.imgSize);
} else {
canvas.remove();
}
this.containers.delete(containerId);
@ -167,9 +173,8 @@ class RLottie {
}
if (!this.params.isLowPriority) {
const currentFrameIndex = Math.floor(this.approxFrameIndex);
this.frames = this.frames.map((frame, i) => {
if (i === currentFrameIndex) {
if (i === this.prevFrameIndex) {
return frame;
} else {
if (frame && frame !== WAITING) {
@ -193,10 +198,41 @@ class RLottie {
this.speed = speed;
}
setNoLoop(noLoop: boolean) {
setNoLoop(noLoop?: boolean) {
this.params.noLoop = noLoop;
}
setSharedCanvasCoords(containerId: string, newCoords: Params['coords']) {
const containerInfo = this.containers.get(containerId)!;
const {
canvas, ctx, isPaused, coords,
} = containerInfo;
if (!canvas.dataset.isJustCleaned || canvas.dataset.isJustCleaned === 'false') {
const { isLowPriority, quality = isLowPriority ? LOW_PRIORITY_QUALITY : HIGH_PRIORITY_QUALITY } = this.params;
const sizeFactor = Math.max(DPR * quality, 1);
ensureCanvasSize(canvas, sizeFactor);
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.dataset.isJustCleaned = 'true';
fastRaf(() => {
canvas.dataset.isJustCleaned = 'false';
});
}
containerInfo.coords = {
x: Math.round((newCoords?.x || 0) * canvas.width),
y: Math.round((newCoords?.y || 0) * canvas.height),
};
if (isPaused || !this.isPlaying()) {
const frame = this.getFrame(this.prevFrameIndex) || this.getFrame(Math.round(this.approxFrameIndex));
if (frame && frame !== WAITING) {
ctx.drawImage(frame, coords!.x, coords!.y);
}
}
}
private addContainer(
containerId: string,
container: HTMLDivElement | HTMLCanvasElement,
@ -251,14 +287,9 @@ class RLottie {
const canvas = container;
const ctx = canvas.getContext('2d')!;
imgSize = Math.round(this.params.size! * sizeFactor);
ensureCanvasSize(canvas, sizeFactor);
const expectedWidth = Math.round(canvas.offsetWidth * sizeFactor);
const expectedHeight = Math.round(canvas.offsetHeight * sizeFactor);
if (canvas.width !== expectedWidth || canvas.height !== expectedHeight) {
canvas.width = expectedWidth;
canvas.height = expectedHeight;
}
imgSize = Math.round(this.params.size! * sizeFactor);
this.containers.set(containerId, {
canvas,
@ -537,4 +568,13 @@ class RLottie {
}
}
function ensureCanvasSize(canvas: HTMLCanvasElement, sizeFactor: number) {
const expectedWidth = Math.round(canvas.offsetWidth * sizeFactor);
const expectedHeight = Math.round(canvas.offsetHeight * sizeFactor);
if (canvas.width !== expectedWidth || canvas.height !== expectedHeight) {
canvas.width = expectedWidth;
canvas.height = expectedHeight;
}
}
export default RLottie;