Created
July 27, 2022 01:50
-
-
Save zaydek/3f8a3cc34261e067b012e0dd7cd13ed9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @refresh reload */ | |
import "./css/reset.scss" | |
import "./css/duomo.scss" | |
import "./css/theme.scss" | |
import * as Solid from "solid-js" | |
import * as SolidWeb from "solid-js/web" | |
import * as helpers from "./helpers" | |
import * as utils from "./utils" | |
//////////////////////////////////////////////////////////////////////////////// | |
function createLocalWindowHeight() { | |
// Cache value as a heuristic | |
const [windowHeight, setWindowHeight] = helpers.createLocalSignal(window.innerHeight, { storageKey: "window.innerHeight" }) | |
function handleResize(e?: UIEvent) { | |
setWindowHeight(window.innerHeight) | |
} | |
handleResize() | |
window.addEventListener("resize", handleResize, false) | |
Solid.onCleanup(() => { | |
window.removeEventListener("resize", handleResize, false) | |
}) | |
return windowHeight | |
} | |
function createLocalScrollY() { | |
// Cache value as a heuristic | |
const [scrollY, setScrollY] = helpers.createLocalSignal(window.scrollY, { storageKey: "window.scrollY" }) | |
function handleScroll(e?: Event) { | |
setScrollY(window.scrollY) | |
} | |
handleScroll() | |
document.addEventListener("scroll", handleScroll, false) | |
Solid.onCleanup(() => { | |
document.removeEventListener("scroll", handleScroll, false) | |
}) | |
return scrollY | |
} | |
function zeroOrMore(value: number) { | |
if (value <= 0) { | |
return 0 | |
} else { | |
return value | |
} | |
} | |
const Debugger = (props: { children: string }) => { | |
return <> | |
<div class="fixed inset-bl no-pointer-events" style="bottom: 24px; left: 24px;"> | |
<div class="p-16 w-448 rounded-16 pointer-events" style="background-color: hsl(0deg 0% 100%); box-shadow: var(--basic-box-shadow);"> | |
<div | |
style={` | |
white-space: pre-wrap; | |
tab-size: 2; | |
font: 400 16px / normal Consolas; | |
`} | |
> | |
{props.children} | |
</div> | |
</div> | |
</div> | |
</> | |
} | |
const VScrollGrid = (props: { | |
__DEBUG?: boolean | |
each: any[] | |
gridItemMinWidth: number | |
gridItemHeight: number | |
scrollBuffer?: | |
| 0 | |
| "1x" | |
| "2x" | |
| "3x" | |
| "4x" | |
children: (_: any, index: () => number) => Solid.JSXElement | |
}) => { | |
const [gridRef, setGridRef] = Solid.createSignal<HTMLElement>() | |
const windowHeight = createLocalWindowHeight() | |
const scrollY = createLocalScrollY() | |
const scrollBuffer = () => { | |
if (typeof props.scrollBuffer === "string") { | |
return windowHeight() * +props.scrollBuffer[0] | |
} else { | |
return 0 | |
} | |
} | |
const [gridWidth, setGridWidth] = Solid.createSignal(0) | |
const columnCount = () => Math.floor(gridWidth() / props.gridItemMinWidth) | |
const [gridTop, setGridTop] = Solid.createSignal(0) | |
const staticGridHeight = () => Math.ceil((props.gridItemHeight * props.each.length) / | |
columnCount()) | |
const rowStart = () => { | |
const scrollPos = scrollY() - scrollBuffer() | |
return zeroOrMore( | |
Math.trunc( | |
Math.min( | |
scrollPos - gridTop(), | |
staticGridHeight(), | |
) / props.gridItemHeight, | |
), | |
) | |
} | |
const rowEndVisible = () => { | |
const scrollPos = scrollY() + windowHeight() + scrollBuffer() | |
return zeroOrMore( | |
Math.ceil( // Use Math.ceil here (not Math.trunc) | |
Math.min( | |
scrollPos - gridTop(), | |
staticGridHeight(), | |
) / props.gridItemHeight, | |
), | |
) | |
} | |
const rowEnd = () => { | |
const scrollPos = scrollY() + windowHeight() | |
return zeroOrMore( | |
-1 * Math.trunc( | |
Math.max( | |
scrollPos - (gridTop() + staticGridHeight()), | |
-1 * staticGridHeight(), | |
) / props.gridItemHeight, | |
), | |
) | |
} | |
const gridItemStart = () => rowStart() * columnCount() | |
const gridItemEnd = () => rowEndVisible() * columnCount() | |
Solid.onMount(() => { | |
function handleResizeW(e?: UIEvent) { | |
Solid.batch(() => { | |
setGridWidth(gridRef()!.offsetWidth) | |
setGridTop(gridRef()!.offsetTop) | |
}) | |
} | |
handleResizeW() | |
window.addEventListener("resize", handleResizeW, false) | |
Solid.onCleanup(() => { | |
window.removeEventListener("resize", handleResizeW, false) | |
}) | |
}) | |
return <> | |
<Solid.Show when={props.__DEBUG}> | |
<Debugger> | |
{JSON.stringify({ | |
rowStart: rowStart(), | |
rowEnd: rowEnd(), | |
gridItemStart: gridItemStart(), | |
gridItemEnd: gridItemEnd(), | |
}, null, 2)} | |
</Debugger> | |
</Solid.Show> | |
<div | |
ref={setGridRef} | |
style={` | |
padding-top: ${rowStart() * props.gridItemHeight}px; | |
padding-bottom: ${rowEnd() * props.gridItemHeight}px; | |
display: grid; | |
grid-template-columns: | |
repeat(auto-fill, minmax(${props.gridItemMinWidth}px, 1fr)); | |
`} | |
> | |
<Solid.For each={props.each.slice(gridItemStart(), gridItemEnd())}> | |
{props.children} | |
</Solid.For> | |
</div> | |
</> | |
} | |
const App = () => { | |
return <> | |
<> | |
<div class="py-224 row center"> | |
<div class="w-full max-w-1024"> | |
<VScrollGrid | |
each={utils.range(100_000)} | |
gridItemHeight={96} | |
gridItemMinWidth={96} | |
scrollBuffer="1x" | |
__DEBUG | |
> | |
{element => <> | |
<div | |
class="row center h-96" | |
style={utils.decomment(` | |
background-color: hsl(0deg 100% 50%); | |
outline: 1px solid hsl(0deg 0% 100%); | |
`)} | |
> | |
{element} | |
</div> | |
</>} | |
</VScrollGrid> | |
</div> | |
</div> | |
</> | |
</> | |
} | |
SolidWeb.render(() => <App />, document.getElementById("root")!) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment