Browse Source

Improve gradient animation

master
Eduard Kuzmenko 2 years ago committed by r4sas
parent
commit
46636e42d7
  1. 19
      src/components/chat/bubbles.ts
  2. 75
      src/components/chat/gradientRenderer.ts
  3. 2
      src/helpers/animation.ts
  4. 15
      src/helpers/fastSmoothScroll.ts

19
src/components/chat/bubbles.ts

@ -264,6 +264,7 @@ export default class ChatBubbles {
private setPeerTempId: number = 0; private setPeerTempId: number = 0;
private renderNewPromises: Set<Promise<any>> = new Set(); private renderNewPromises: Set<Promise<any>> = new Set();
private updateGradient: boolean;
// private reactions: Map<number, ReactionsElement>; // private reactions: Map<number, ReactionsElement>;
@ -887,18 +888,15 @@ export default class ChatBubbles {
this.listenerSetter.add(rootScope)('history_append', async({storageKey, message}) => { this.listenerSetter.add(rootScope)('history_append', async({storageKey, message}) => {
if(storageKey !== this.chat.messagesStorageKey) return; if(storageKey !== this.chat.messagesStorageKey) return;
if(rootScope.settings.animationsEnabled) {
this.updateGradient = true;
}
if(!this.scrollable.loadedAll.bottom) { if(!this.scrollable.loadedAll.bottom) {
this.chat.setMessageId(); this.chat.setMessageId();
} else { } else {
this.renderNewMessage(message, true); this.renderNewMessage(message, true);
} }
if(rootScope.settings.animationsEnabled) {
const gradientRenderer = this.chat.gradientRenderer;
if(gradientRenderer) {
gradientRenderer.toNextPosition();
}
}
}); });
this.listenerSetter.add(rootScope)('history_multiappend', (message) => { this.listenerSetter.add(rootScope)('history_multiappend', (message) => {
@ -2375,6 +2373,12 @@ export default class ChatBubbles {
startCallback: (dimensions) => { startCallback: (dimensions) => {
// this.onScroll(true, this.scrolledDown && dimensions.distanceToEnd <= SCROLLED_DOWN_THRESHOLD ? undefined : dimensions); // this.onScroll(true, this.scrolledDown && dimensions.distanceToEnd <= SCROLLED_DOWN_THRESHOLD ? undefined : dimensions);
this.onScroll(true, dimensions); this.onScroll(true, dimensions);
if(this.updateGradient) {
const {gradientRenderer} = this.chat;
gradientRenderer?.toNextPosition(dimensions.getProgress);
this.updateGradient = undefined;
}
} }
}); });
@ -2604,6 +2608,7 @@ export default class ChatBubbles {
this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined; this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined;
this.fetchNewPromise = undefined; this.fetchNewPromise = undefined;
this.getSponsoredMessagePromise = undefined; this.getSponsoredMessagePromise = undefined;
this.updateGradient = undefined;
if(this.stickyIntersector) { if(this.stickyIntersector) {
this.stickyIntersector.disconnect(); this.stickyIntersector.disconnect();

75
src/components/chat/gradientRenderer.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import {animate} from '../../helpers/animation'; import {animateSingle} from '../../helpers/animation';
import {hexToRgb} from '../../helpers/color'; import {hexToRgb} from '../../helpers/color';
const WIDTH = 50; const WIDTH = 50;
@ -19,13 +19,6 @@ export default class ChatBackgroundGradientRenderer {
private readonly _scrollTails = 50; private readonly _scrollTails = 50;
private _frames: ImageData[]; private _frames: ImageData[];
private _colors: {r: number, g: number, b: number}[]; private _colors: {r: number, g: number, b: number}[];
/* private readonly _curve = [
0, 25, 50, 75, 100, 150, 200, 250, 300, 350, 400, 500, 600, 700, 800, 900,
1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1830, 1860, 1890, 1920,
1950, 1980, 2010, 2040, 2070, 2100, 2130, 2160, 2190, 2220, 2250, 2280, 2310,
2340, 2370, 2400, 2430, 2460, 2490, 2520, 2550, 2580, 2610, 2630, 2640, 2650,
2660, 2670, 2680, 2690, 2700
]; */
private readonly _curve = [ private readonly _curve = [
0, 0.25, 0.50, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0.25, 0.50, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 18.3, 18.6, 18.9, 19.2, 19.5, 19.8, 20.1, 20.4, 20.7, 13, 14, 15, 16, 17, 18, 18.3, 18.6, 18.9, 19.2, 19.5, 19.8, 20.1, 20.4, 20.7,
@ -59,6 +52,9 @@ export default class ChatBackgroundGradientRenderer {
private _addedScrollListener: boolean; private _addedScrollListener: boolean;
private _animatingToNextPosition: boolean; private _animatingToNextPosition: boolean;
private _nextPositionTail: number;
private _nextPositionTails: number;
private _nextPositionLeft: number;
constructor() { constructor() {
const diff = this._tails / this._curve[this._curve.length - 1]; const diff = this._tails / this._curve[this._curve.length - 1];
@ -79,10 +75,7 @@ export default class ChatBackgroundGradientRenderer {
private getPositions(shift: number) { private getPositions(shift: number) {
const positions = this._positions.slice(); const positions = this._positions.slice();
while(shift > 0) { positions.push(...positions.splice(0, shift));
positions.push(positions.shift());
--shift;
}
const result: typeof positions = []; const result: typeof positions = [];
for(let i = 0; i < positions.length; i += 2) { for(let i = 0; i < positions.length; i += 2) {
@ -151,31 +144,52 @@ export default class ChatBackgroundGradientRenderer {
} }
}; };
private drawOnWheel = () => { private changeTailAndDraw(diff: number) {
let diff = this._scrollDelta / this._scrollTails;
this._scrollDelta %= this._scrollTails;
diff = diff > 0 ? Math.floor(diff) : Math.ceil(diff);
if(diff) {
this.changeTail(diff); this.changeTail(diff);
const curPos = this.curPosition(this._phase, this._tail); const curPos = this.curPosition(this._phase, this._tail);
this.drawGradient(curPos); this.drawGradient(curPos);
} }
private drawOnWheel = () => {
const value = this._scrollDelta / this._scrollTails;
this._scrollDelta %= this._scrollTails;
const diff = value > 0 ? Math.floor(value) : Math.ceil(value);
if(diff) {
this.changeTailAndDraw(diff);
}
this._onWheelRAF = undefined; this._onWheelRAF = undefined;
}; };
private drawNextPositionAnimated = () => { private drawNextPositionAnimated = (getProgress?: () => number) => {
let done: boolean, id: ImageData;
if(getProgress) {
const value = getProgress();
done = value >= 1;
const nextPositionTail = this._nextPositionTail ?? 0;
const tail = this._nextPositionTail = this._nextPositionTails * value;
const diff = tail - nextPositionTail;
if(diff) {
this._nextPositionLeft -= diff;
this.changeTailAndDraw(diff);
}
} else {
const frames = this._frames; const frames = this._frames;
const id = frames.shift(); id = frames.shift();
done = !frames.length;
}
if(id) { if(id) {
this.drawImageData(id); this.drawImageData(id);
} }
const leftLength = frames.length; if(done) {
if(!leftLength) { this._nextPositionLeft = undefined;
this._nextPositionTails = undefined;
this._nextPositionTail = undefined;
this._animatingToNextPosition = undefined; this._animatingToNextPosition = undefined;
} }
return !!leftLength; return !done;
}; };
private getGradientImageData(positions: {x: number, y: number}[]) { private getGradientImageData(positions: {x: number, y: number}[]) {
@ -303,11 +317,20 @@ export default class ChatBackgroundGradientRenderer {
this.drawGradient(pos); this.drawGradient(pos);
} }
public toNextPosition() { public toNextPosition(getProgress?: () => number) {
if(this._colors.length < 2) { if(this._colors.length < 2) {
return; return;
} }
if(getProgress) {
this._nextPositionLeft = this._tails + (this._nextPositionLeft ?? 0);
this._nextPositionTails = this._nextPositionLeft;
this._nextPositionTail = undefined;
this._animatingToNextPosition = true;
animateSingle(this.drawNextPositionAnimated.bind(this, getProgress), this);
return;
}
const tail = this._tail; const tail = this._tail;
const tails = this._tails; const tails = this._tails;
@ -356,10 +379,14 @@ export default class ChatBackgroundGradientRenderer {
}); });
this._animatingToNextPosition = true; this._animatingToNextPosition = true;
animate(this.drawNextPositionAnimated); animateSingle(this.drawNextPositionAnimated, this);
} }
// public toNextPositionThrottled = throttle(this.toNextPosition.bind(this), 100, true);
public scrollAnimate(start?: boolean) { public scrollAnimate(start?: boolean) {
// return;
if(this._colors.length < 2 && start) { if(this._colors.length < 2 && start) {
return; return;
} }

2
src/helpers/animation.ts

@ -27,7 +27,9 @@ export function createAnimationInstance(key: AnimationInstanceKey) {
instances.set(key, instance); instances.set(key, instance);
instance.deferred.then(() => { instance.deferred.then(() => {
if(getAnimationInstance(key) === instance) {
instances.delete(key); instances.delete(key);
}
}); });
return instance; return instance;

15
src/helpers/fastSmoothScroll.ts

@ -32,6 +32,7 @@ export type ScrollStartCallbackDimensions = {
duration: number, duration: number,
containerRect: DOMRect, containerRect: DOMRect,
elementRect: DOMRect, elementRect: DOMRect,
getProgress: () => number
}; };
export type ScrollOptions = { export type ScrollOptions = {
@ -243,13 +244,16 @@ function scrollWithJs(options: ScrollOptions): Promise<void> {
*/ */
const transition = absPath < SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition; const transition = absPath < SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition;
const tick = () => { const getProgress = () => {
const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1; const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
return transition(t);
const currentPath = path * (1 - transition(t)); };
const tick = () => {
const value = getProgress();
const currentPath = path * (1 - value);
container[scrollPositionKey] = Math.round(target - currentPath); container[scrollPositionKey] = Math.round(target - currentPath);
return t < 1; return value < 1;
}; };
if(!duration || !path) { if(!duration || !path) {
@ -285,7 +289,8 @@ function scrollWithJs(options: ScrollOptions): Promise<void> {
path, path,
duration, duration,
containerRect, containerRect,
elementRect elementRect,
getProgress
}); });
} }

Loading…
Cancel
Save