You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.3 KiB
124 lines
3.3 KiB
<script lang="ts">
|
|
import { onDestroy, onMount } from 'svelte';
|
|
import { settings, appState } from '../stores'
|
|
|
|
function getOffset(el: HTMLElement | null) {
|
|
var _x = 0;
|
|
var _y = 0;
|
|
while(el && el instanceof HTMLElement) {
|
|
_x += el.offsetLeft - el.scrollLeft;
|
|
_y += el.offsetTop - el.scrollTop;
|
|
el = el.offsetParent as HTMLElement;
|
|
}
|
|
return { top: _y, left: _x };
|
|
}
|
|
|
|
let positions:[number, number, number, string][] = [];
|
|
const getViewport = () => (typeof visualViewport != "undefined" ? () => [visualViewport.width, visualViewport.height] : () => [document.documentElement.clientWidth, document.documentElement.clientHeight])();
|
|
const getDistFromTop = () => (typeof visualViewport != "undefined" ? () => visualViewport.pageTop : () => document.documentElement.scrollTop)();
|
|
let viewhint: HTMLSpanElement;
|
|
|
|
const updatePositions = (v: typeof $appState) => {
|
|
const [sw, sh] = getViewport();
|
|
const containerScrollHeight = document.documentElement.scrollHeight;
|
|
positions = v.foundPosts.map(v => {
|
|
const coords = getOffset(v);
|
|
const top = sh * (coords.top / containerScrollHeight);
|
|
const bot = sh * ((coords.top + v.offsetHeight) / containerScrollHeight);
|
|
const hei = (bot - top);
|
|
return [top, hei, coords.top, getComputedStyle(v)['borderRightColor']];
|
|
})
|
|
}
|
|
|
|
const updateViewhint = () => {
|
|
if (!$settings.sh) return;
|
|
const [sw, sh] = getViewport();
|
|
const fromtop = getDistFromTop();
|
|
const containerScrollHeight = document.documentElement.scrollHeight;
|
|
const top = sh * (fromtop / containerScrollHeight);
|
|
const bot = sh * ((fromtop + sh) / containerScrollHeight);
|
|
const hei = (bot - top);
|
|
viewhint.style.top = top + 'px';
|
|
viewhint.style.height = hei + 'px';
|
|
}
|
|
|
|
appState.subscribe(v => updatePositions(v));
|
|
|
|
const handleResize = () => {
|
|
updatePositions($appState);
|
|
};
|
|
|
|
let locked = false;
|
|
const handleScroll = async () => {
|
|
if (locked) return;
|
|
locked = true;
|
|
updateViewhint();
|
|
await new Promise(_ => requestAnimationFrame(_));
|
|
locked = false;
|
|
};
|
|
|
|
const docRszObserver = new ResizeObserver(e => {
|
|
updatePositions($appState)
|
|
updateViewhint();
|
|
});
|
|
|
|
onMount(() => {
|
|
window.addEventListener('resize', handleResize);
|
|
document.addEventListener('scroll', handleScroll);
|
|
updateViewhint();
|
|
docRszObserver.observe(document.documentElement);
|
|
});
|
|
|
|
onDestroy(() => {
|
|
window.removeEventListener('resize', handleResize);
|
|
document.addEventListener('scroll', handleScroll);
|
|
docRszObserver.unobserve(document.documentElement);
|
|
})
|
|
|
|
</script>
|
|
|
|
{#if $settings.sh}
|
|
<div class="scroll-container">
|
|
{#each $appState.foundPosts as post, i}
|
|
<span
|
|
on:click={() => window.scrollTo(0, positions[i][2])}
|
|
style="top: {positions[i][0]}px; height: {positions[
|
|
i
|
|
][1]}px; background-color: {positions[i][3]}"
|
|
class="marker"
|
|
/>
|
|
{/each}
|
|
<span class="hint" bind:this={viewhint} />
|
|
</div>
|
|
{/if}
|
|
|
|
<style scoped>
|
|
.hint {
|
|
background-color: rgb(222 222 222 / 80%);
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.scroll-container {
|
|
position: fixed;
|
|
height: 100%;
|
|
width: 12px;
|
|
/* pointer-events: none; */
|
|
top: 0;
|
|
right: 0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.scroll-container span {
|
|
/* markers */
|
|
position: absolute;
|
|
right: 0;
|
|
width: 33%;
|
|
cursor: pointer;
|
|
transition: width 200ms;
|
|
}
|
|
|
|
.scroll-container:hover span {
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
|