|
|
|
/*
|
|
|
|
* https://github.com/morethanwords/tweb
|
|
|
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
|
|
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
import rootScope from "../lib/rootScope";
|
|
|
|
import { IS_SAFARI } from "../environment/userAgent";
|
|
|
|
import { MOUNT_CLASS_TO } from "../config/debug";
|
|
|
|
import isInDOM from "../helpers/dom/isInDOM";
|
|
|
|
import RLottiePlayer from "../lib/rlottie/rlottiePlayer";
|
|
|
|
import indexOfAndSplice from "../helpers/array/indexOfAndSplice";
|
|
|
|
import forEachReverse from "../helpers/array/forEachReverse";
|
|
|
|
|
|
|
|
export interface AnimationItem {
|
|
|
|
el: HTMLElement,
|
|
|
|
group: string,
|
|
|
|
animation: RLottiePlayer | HTMLVideoElement
|
|
|
|
};
|
|
|
|
|
|
|
|
export class AnimationIntersector {
|
|
|
|
private observer: IntersectionObserver;
|
|
|
|
private visible: Set<AnimationItem> = new Set();
|
|
|
|
|
|
|
|
private overrideIdleGroups: Set<string>;
|
|
|
|
private byGroups: {[group: string]: AnimationItem[]} = {};
|
|
|
|
private lockedGroups: {[group: string]: true} = {};
|
|
|
|
private onlyOnePlayableGroup: string = '';
|
|
|
|
|
|
|
|
private intersectionLockedGroups: {[group: string]: true} = {};
|
|
|
|
private videosLocked = false;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.observer = new IntersectionObserver((entries) => {
|
|
|
|
// if(rootScope.idle.isIDLE) return;
|
|
|
|
|
|
|
|
for(const entry of entries) {
|
|
|
|
const target = entry.target;
|
|
|
|
|
|
|
|
for(const group in this.byGroups) {
|
|
|
|
if(this.intersectionLockedGroups[group]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const player = this.byGroups[group].find(p => p.el === target);
|
|
|
|
if(player) {
|
|
|
|
if(entry.isIntersecting) {
|
|
|
|
this.visible.add(player);
|
|
|
|
this.checkAnimation(player, false);
|
|
|
|
} else {
|
|
|
|
this.visible.delete(player);
|
|
|
|
this.checkAnimation(player, true);
|
|
|
|
|
|
|
|
if(player.animation instanceof RLottiePlayer/* && player.animation.cachingDelta === 2 */) {
|
|
|
|
//console.warn('will clear cache', player);
|
|
|
|
player.animation.clearCache();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.overrideIdleGroups = new Set();
|
|
|
|
|
|
|
|
rootScope.addEventListener('media_play', ({doc}) => {
|
|
|
|
if(doc.type === 'round') {
|
|
|
|
this.videosLocked = true;
|
|
|
|
this.checkAnimations();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
rootScope.addEventListener('media_pause', () => {
|
|
|
|
if(this.videosLocked) {
|
|
|
|
this.videosLocked = false;
|
|
|
|
this.checkAnimations();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public setOverrideIdleGroup(group: string, override: boolean) {
|
|
|
|
if(override) this.overrideIdleGroups.add(group);
|
|
|
|
else this.overrideIdleGroups.delete(group);
|
|
|
|
}
|
|
|
|
|
|
|
|
public getAnimations(element: HTMLElement) {
|
|
|
|
const found: AnimationItem[] = [];
|
|
|
|
for(const group in this.byGroups) {
|
|
|
|
for(const player of this.byGroups[group]) {
|
|
|
|
if(player.el === element) {
|
|
|
|
found.push(player);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
public removeAnimation(player: AnimationItem) {
|
|
|
|
//console.log('destroy animation');
|
|
|
|
const {el, animation} = player;
|
|
|
|
animation.remove();
|
|
|
|
|
|
|
|
if(animation instanceof HTMLVideoElement && IS_SAFARI) {
|
|
|
|
setTimeout(() => { // TODO: очистка по очереди, а не все вместе с этим таймаутом
|
|
|
|
animation.src = '';
|
|
|
|
animation.load();
|
|
|
|
}, 1e3);
|
|
|
|
}
|
|
|
|
|
|
|
|
const group = this.byGroups[player.group];
|
|
|
|
if(group) {
|
|
|
|
indexOfAndSplice(group, player);
|
|
|
|
if(!group.length) {
|
|
|
|
delete this.byGroups[player.group];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.observer.unobserve(el);
|
|
|
|
this.visible.delete(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group = '') {
|
|
|
|
const player = {
|
|
|
|
el: animation instanceof RLottiePlayer ? animation.el : animation,
|
|
|
|
animation: animation,
|
|
|
|
group
|
|
|
|
};
|
|
|
|
|
|
|
|
if(animation instanceof RLottiePlayer) {
|
|
|
|
if(!rootScope.settings.stickers.loop && animation.loop) {
|
|
|
|
animation.loop = rootScope.settings.stickers.loop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(this.byGroups[group] ?? (this.byGroups[group] = [])).push(player);
|
|
|
|
this.observer.observe(player.el);
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
|
|
|
|
// if(rootScope.idle.isIDLE) return;
|
|
|
|
|
|
|
|
if(group !== undefined && !this.byGroups[group]) {
|
|
|
|
//console.warn('no animation group:', group);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups);
|
|
|
|
|
|
|
|
for(const group of groups) {
|
|
|
|
const animations = this.byGroups[group];
|
|
|
|
|
|
|
|
forEachReverse(animations, (player) => {
|
|
|
|
this.checkAnimation(player, blurred, destroy);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
|
|
|
|
const {el, animation, group} = player;
|
|
|
|
//return;
|
|
|
|
if((destroy || (!isInDOM(el) && !this.lockedGroups[group]))/* && false */) {
|
|
|
|
this.removeAnimation(player);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group) || (animation instanceof HTMLVideoElement && this.videosLocked)) {
|
|
|
|
if(!animation.paused) {
|
|
|
|
//console.warn('pause animation:', animation);
|
|
|
|
animation.pause();
|
|
|
|
}
|
|
|
|
} else if(animation.paused &&
|
|
|
|
this.visible.has(player) &&
|
|
|
|
animation.autoplay &&
|
|
|
|
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group) &&
|
|
|
|
(!rootScope.idle.isIDLE || this.overrideIdleGroups.has(player.group))
|
|
|
|
) {
|
|
|
|
//console.warn('play animation:', animation);
|
|
|
|
animation.play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public setOnlyOnePlayableGroup(group: string) {
|
|
|
|
this.onlyOnePlayableGroup = group;
|
|
|
|
}
|
|
|
|
|
|
|
|
public lockGroup(group: string) {
|
|
|
|
this.lockedGroups[group] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public unlockGroup(group: string) {
|
|
|
|
delete this.lockedGroups[group];
|
|
|
|
this.checkAnimations(undefined, group);
|
|
|
|
}
|
|
|
|
|
|
|
|
public refreshGroup(group: string) {
|
|
|
|
const animations = this.byGroups[group];
|
|
|
|
if(animations && animations.length) {
|
|
|
|
animations.forEach(animation => {
|
|
|
|
this.observer.unobserve(animation.el);
|
|
|
|
});
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
animations.forEach(animation => {
|
|
|
|
this.observer.observe(animation.el);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public lockIntersectionGroup(group: string) {
|
|
|
|
this.intersectionLockedGroups[group] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public unlockIntersectionGroup(group: string) {
|
|
|
|
delete this.intersectionLockedGroups[group];
|
|
|
|
this.refreshGroup(group);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const animationIntersector = new AnimationIntersector();
|
|
|
|
if(MOUNT_CLASS_TO) {
|
|
|
|
MOUNT_CLASS_TO.animationIntersector = animationIntersector;
|
|
|
|
}
|
|
|
|
export default animationIntersector;
|