line-and-surface/src/components/VideoScroll.svelte

155 lines
4.0 KiB
Svelte

<script lang="ts" context="module">
export enum VideoScrollDirection {
RIGHT = "right",
LEFT = "left",
UP = "up",
DOWN = "down",
}
export interface VideoScrollDef {
id: string;
top: number;
left: number;
angle: number;
width: number;
height: number;
directions: VideoScrollDirection[];
files: string[];
}
</script>
<script lang="ts">
import { rotate } from "../utils";
import { onMount } from "svelte";
export let definition: VideoScrollDef;
let root: HTMLDivElement;
let dynamicFiles: { id: string; top: number; left: number; src: string }[] =
[];
$: {
dynamicFiles = definition.files.slice(1).map((src: string, idx: number) => {
const id = `${idx}_${src}`;
const cy =
definition.top +
(isVertical ? definition.height * (idx + 1) * verticalDirection : 0);
const cx =
definition.left +
(isHorizontal ? definition.width * (idx + 1) * horizontalDirection : 0);
const [left, top] = rotate(
cx,
cy,
definition.left,
definition.top,
definition.angle
);
return { id, top, left, src };
});
}
$: isHorizontal = definition.directions.some(
(dir: VideoScrollDirection) =>
dir === VideoScrollDirection.LEFT || dir === VideoScrollDirection.RIGHT
);
$: isVertical = definition.directions.some(
(dir: VideoScrollDirection) =>
dir === VideoScrollDirection.UP || dir === VideoScrollDirection.DOWN
);
$: horizontalDirection = definition.directions.includes(
VideoScrollDirection.RIGHT
)
? 1
: -1;
$: verticalDirection = definition.directions.includes(
VideoScrollDirection.DOWN
)
? 1
: -1;
onMount(() => {
const observer = new IntersectionObserver((entries, _) => {
entries.forEach((entry) => {
const element = entry.target as HTMLImageElement;
if (entry.isIntersecting) {
element.classList.add("visible");
if (!element.src) {
console.debug(
`[VIDEOSCROLL] Intersected, loading ${element.dataset.src}`
);
element.src = element.dataset.src!;
setTimeout(() => {
element.classList.add("displayed");
}, 3000);
element.onload = () => {
element.classList.add("displayed");
element.classList.add("loaded");
if (isHorizontal) {
element.style.height = "auto";
} else {
element.style.width = "auto";
}
};
}
} else {
element.classList.remove("visible");
}
});
});
if (root) {
Array.from(root.children).forEach((el) => {
observer.observe(el);
});
}
});
</script>
{#if definition.directions.length > 0}
<div class="video-scroll" bind:this={root}>
<img
class="visible displayed loaded"
src={definition.files[0]}
style:top="{Math.round(definition.top)}px"
style:left="{Math.round(definition.left)}px"
style:width={isHorizontal ? `${Math.round(definition.width)}px` : "auto"}
style:height={isVertical ? `${Math.round(definition.height)}px` : "auto"}
style:transform="rotate({definition.angle}deg)"
alt=""
/>
{#each dynamicFiles as file (file.id)}
<img
data-src={file.src}
style:top="{Math.round(file.top)}px"
style:left="{Math.round(file.left)}px"
style:width="{Math.round(definition.width)}px"
style:height="{Math.round(definition.height)}px"
style:transform="rotate({definition.angle}deg)"
alt=""
/>
{/each}
</div>
{/if}
<style lang="scss">
.video-scroll img {
position: absolute;
image-rendering: optimizeSpeed;
background: grey;
visibility: hidden;
opacity: 0;
transition: opacity 0.5s;
&.visible {
visibility: visible !important;
}
&.displayed {
opacity: 1 !important;
}
&.loaded {
background: transparent !important;
}
}
</style>