import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { TailwindElement } from '../shared/tailwind.element';
import Style from './scroll.component.scss?inline';
import { preload } from '../utils/preload';
import { updateText } from '../utils/updateText';
import { setupCanvas } from '../utils/setupCanvas';
import { debounce } from '../utils/debounce';
import {
defaultDataSlots,
defaultDesktopImages,
defaultMobileImages,
} from '../constants/scroll.variant';
import { dataSlots } from '../types/scroll.component';
@customElement('ymlwebcl-scroll')
export class ScrollComponent extends TailwindElement(Style) {
@property({ type: Array, reflect: true })
dataSlot: Array = defaultDataSlots;
@property({ type: Object, reflect: true })
desktopImages: object = defaultDesktopImages;
@property({ type: Object, reflect: true })
mobileImages: object = defaultMobileImages;
protected firstUpdated() {
const container = document.body
.getElementsByTagName('ymlwebcl-scroll')[0]
.shadowRoot.querySelector('.scrub-scroll') as HTMLElement;
const elements = container.querySelectorAll('.scroll-reveal');
const canvas = container.querySelector('canvas') as HTMLCanvasElement;
const context = canvas.getContext('2d');
let images: HTMLImageElement[] = [];
let lastDrawnFrame = -1;
const drawFrame = (frameIndex: number) => {
if (lastDrawnFrame === frameIndex || !images[frameIndex]?.complete)
return;
context?.drawImage(images[frameIndex], 0, 0);
lastDrawnFrame = frameIndex;
};
const handleScroll = () => {
const maxScrollTop = container.scrollHeight - window.innerHeight;
const scrollFraction = document.documentElement.scrollTop / maxScrollTop;
let frameIndex = Math.min(
images.length - 1,
Math.ceil(scrollFraction * images.length)
);
if (scrollFraction > 1) {
elements.forEach((el) => el.classList.remove('active'));
canvas.classList.add('scroll-finished');
frameIndex = images.length - 1;
return;
}
canvas.classList.remove('scroll-finished');
requestAnimationFrame(() => {
elements.forEach((el) => updateText(el, frameIndex));
drawFrame(frameIndex);
});
};
const handleResize = async () => {
let renderForced = false;
images = await preload(this.desktopImages, this.mobileImages);
await setupCanvas(canvas);
const forceReRender = () => {
renderForced = true;
lastDrawnFrame = -1;
handleScroll();
};
images[2].onload = forceReRender;
if (images[2].complete && !renderForced) {
forceReRender();
}
};
const init = async () => {
setupCanvas(canvas);
images = await preload(this.desktopImages, this.mobileImages);
images[2].onload = handleScroll;
};
history.scrollRestoration = 'manual';
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
window.addEventListener('load', handleScroll, { passive: true });
window.addEventListener('scroll', handleScroll, { passive: true });
window.addEventListener('resize', debounce(handleResize), {
passive: true,
});
init();
}
render() {
return html`
`;
}
}