155 lines
4.0 KiB
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>
|