Properly handle shared canvas resize
This commit is contained in:
parent
ee73e1ba73
commit
dfe7b80a6b
@ -209,11 +209,17 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
}, [unfreezeAnimation]);
|
}, [unfreezeAnimation]);
|
||||||
|
|
||||||
useOnChange(([prevNoLoop]) => {
|
useOnChange(([prevNoLoop]) => {
|
||||||
if (noLoop !== undefined && noLoop !== prevNoLoop) {
|
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {
|
||||||
animation?.setNoLoop(noLoop);
|
animation?.setNoLoop(noLoop);
|
||||||
}
|
}
|
||||||
}, [noLoop, animation]);
|
}, [noLoop, animation]);
|
||||||
|
|
||||||
|
useOnChange(([prevSharedCanvasCoords]) => {
|
||||||
|
if (prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords) {
|
||||||
|
animation?.setSharedCanvasCoords(containerId, sharedCanvasCoords);
|
||||||
|
}
|
||||||
|
}, [sharedCanvasCoords, containerId, animation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!animation) {
|
if (!animation) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
.thumb {
|
.thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media {
|
.media {
|
||||||
@ -42,7 +43,6 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
|
||||||
user-select: auto !important;
|
user-select: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
export default function useSharedCanvasCoords(
|
||||||
containerRef: React.RefObject<HTMLDivElement>,
|
containerRef: React.RefObject<HTMLDivElement>,
|
||||||
@ -7,20 +11,42 @@ export default function useSharedCanvasCoords(
|
|||||||
const [x, setX] = useState<number>();
|
const [x, setX] = useState<number>();
|
||||||
const [y, setY] = useState<number>();
|
const [y, setY] = useState<number>();
|
||||||
|
|
||||||
useEffect(() => {
|
const recalculate = useCallback(() => {
|
||||||
if (!sharedCanvasRef?.current) {
|
const container = containerRef.current;
|
||||||
|
const canvas = sharedCanvasRef?.current;
|
||||||
|
if (!container || !canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = containerRef.current!;
|
|
||||||
const target = container.classList.contains('sticker-set-cover') ? container : container.querySelector('img')!;
|
const target = container.classList.contains('sticker-set-cover') ? container : container.querySelector('img')!;
|
||||||
const targetBounds = target.getBoundingClientRect();
|
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)
|
// Factor coords are used to support rendering while being rescaled (e.g. message appearance animation)
|
||||||
setX((targetBounds.left - canvasBounds.left) / canvasBounds.width);
|
setX(round((targetBounds.left - canvasBounds.left) / canvasBounds.width, 4));
|
||||||
setY((targetBounds.top - canvasBounds.top) / canvasBounds.height);
|
setY(round((targetBounds.top - canvasBounds.top) / canvasBounds.height, 4));
|
||||||
}, [containerRef, sharedCanvasRef]);
|
}, [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]);
|
return useMemo(() => (x !== undefined && y !== undefined ? { x, y } : undefined), [x, y]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import WorkerConnector from '../../util/WorkerConnector';
|
import WorkerConnector from '../../util/WorkerConnector';
|
||||||
import { animate } from '../../util/animation';
|
import { animate } from '../../util/animation';
|
||||||
import cycleRestrict from '../../util/cycleRestrict';
|
import cycleRestrict from '../../util/cycleRestrict';
|
||||||
|
import { fastRaf } from '../../util/schedulers';
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
noLoop?: boolean;
|
noLoop?: boolean;
|
||||||
@ -120,9 +121,14 @@ class RLottie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public removeContainer(containerId: string) {
|
public removeContainer(containerId: string) {
|
||||||
const containerData = this.containers.get(containerId)!;
|
const {
|
||||||
if (!containerData.isSharedCanvas) {
|
canvas, ctx, isSharedCanvas, coords,
|
||||||
this.containers.get(containerId)!.canvas.remove();
|
} = this.containers.get(containerId)!;
|
||||||
|
|
||||||
|
if (isSharedCanvas) {
|
||||||
|
ctx.clearRect(coords!.x, coords!.y, this.imgSize, this.imgSize);
|
||||||
|
} else {
|
||||||
|
canvas.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.containers.delete(containerId);
|
this.containers.delete(containerId);
|
||||||
@ -167,9 +173,8 @@ class RLottie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.params.isLowPriority) {
|
if (!this.params.isLowPriority) {
|
||||||
const currentFrameIndex = Math.floor(this.approxFrameIndex);
|
|
||||||
this.frames = this.frames.map((frame, i) => {
|
this.frames = this.frames.map((frame, i) => {
|
||||||
if (i === currentFrameIndex) {
|
if (i === this.prevFrameIndex) {
|
||||||
return frame;
|
return frame;
|
||||||
} else {
|
} else {
|
||||||
if (frame && frame !== WAITING) {
|
if (frame && frame !== WAITING) {
|
||||||
@ -193,10 +198,41 @@ class RLottie {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
setNoLoop(noLoop: boolean) {
|
setNoLoop(noLoop?: boolean) {
|
||||||
this.params.noLoop = noLoop;
|
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(
|
private addContainer(
|
||||||
containerId: string,
|
containerId: string,
|
||||||
container: HTMLDivElement | HTMLCanvasElement,
|
container: HTMLDivElement | HTMLCanvasElement,
|
||||||
@ -251,14 +287,9 @@ class RLottie {
|
|||||||
const canvas = container;
|
const canvas = container;
|
||||||
const ctx = canvas.getContext('2d')!;
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
imgSize = Math.round(this.params.size! * sizeFactor);
|
ensureCanvasSize(canvas, sizeFactor);
|
||||||
|
|
||||||
const expectedWidth = Math.round(canvas.offsetWidth * sizeFactor);
|
imgSize = Math.round(this.params.size! * sizeFactor);
|
||||||
const expectedHeight = Math.round(canvas.offsetHeight * sizeFactor);
|
|
||||||
if (canvas.width !== expectedWidth || canvas.height !== expectedHeight) {
|
|
||||||
canvas.width = expectedWidth;
|
|
||||||
canvas.height = expectedHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.containers.set(containerId, {
|
this.containers.set(containerId, {
|
||||||
canvas,
|
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;
|
export default RLottie;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user