From 74873645713fdefba82f5177be3e4128758769a6 Mon Sep 17 00:00:00 2001 From: Aaron Gutierrez Date: Sat, 29 Dec 2018 11:23:40 -0800 Subject: [PATCH] defer image loading --- src/components/grid.tsx | 11 +++++++++ src/components/image_set.tsx | 21 +++++++++++++++- src/components/picture.tsx | 46 +++++++++++++++++++++++++++--------- src/components/root.tsx | 39 ++++++++++++++++++++++++++---- 4 files changed, 101 insertions(+), 16 deletions(-) diff --git a/src/components/grid.tsx b/src/components/grid.tsx index dd427ae..7012f81 100644 --- a/src/components/grid.tsx +++ b/src/components/grid.tsx @@ -6,6 +6,7 @@ import * as React from "react"; export interface Props { images: Model.Image[]; onImageSelected: (image: Model.Image) => void; + pageBottom: number; width: number; } @@ -20,7 +21,11 @@ interface Row { export class Grid extends React.PureComponent { static displayName = "Grid"; + private gridHeight = 0; + render() { + this.gridHeight = 0; + let row: Model.Image[] = []; const rows: Row[] = []; let rowWidth = 0; @@ -57,9 +62,15 @@ export class Grid extends React.PureComponent { onClick={() => this.props.onImageSelected(image)} key={image.src} width={(image.width / image.height) * height} + defer={ + this.gridHeight > this.props.pageBottom + 2 * this._rowHeight() + } /> ); }); + + this.gridHeight += height; + return (
void; + setGridHeight: (height: number) => void; + pageBottom: number; width: number; } export class ImageSet extends React.PureComponent { static displayName = "ImageSet"; + private divRef: React.RefObject = React.createRef(); + render() { return ( -
+

{this.props.imageSet.location} ยท {this.props.imageSet.description}

); } + + componentDidMount() { + this._setGridHeight(); + } + + componentDidUpdate() { + this._setGridHeight(); + } + + private _setGridHeight = () => { + if (this.divRef.current) { + this.props.setGridHeight(this.divRef.current.clientHeight); + } + }; } diff --git a/src/components/picture.tsx b/src/components/picture.tsx index 0cbd05a..4aa4741 100644 --- a/src/components/picture.tsx +++ b/src/components/picture.tsx @@ -6,19 +6,43 @@ export interface Props { image: Model.Image; onClick: () => void; width: number; + defer?: boolean; +} + +export interface State { + hasLoadedOnce: boolean; } interface SrcSetInfo { - srcSet: string; - bestSrc: string; + srcSet: string; + bestSrc: string; } -export class Picture extends React.PureComponent { +export class Picture extends React.PureComponent { static displayName = "Picture"; + state: State = { + hasLoadedOnce: false + }; + + static getDerivedStateFromProps(props: Props, state: State): State | null { + return !state.hasLoadedOnce && !props.defer + ? { hasLoadedOnce: true } + : null; + } + render() { + if (this.props.defer && !this.state.hasLoadedOnce) { + return ( +
+ ); + } + const srcSet = this._srcset(); - + return ( { if (scale >= 1) { srcs.push(`img/${size}/${this.props.image.src} ${scale}x`); - if (scale < bestRatio) { - bestSize = size; - bestRatio = scale; - } + if (scale < bestRatio) { + bestSize = size; + bestRatio = scale; + } } }); - srcs.push(`img/${bestSize}/${this.props.image.src} 1x`); + srcs.push(`img/${bestSize}/${this.props.image.src} 1x`); return { - srcSet: srcs.join(","), - bestSrc: `img/${bestSize}/${this.props.image.src}` + srcSet: srcs.join(","), + bestSrc: `img/${bestSize}/${this.props.image.src}` }; }; } diff --git a/src/components/root.tsx b/src/components/root.tsx index c86e187..61e64a2 100644 --- a/src/components/root.tsx +++ b/src/components/root.tsx @@ -9,13 +9,19 @@ export interface Props {} export interface State { data?: Model.Data | null; selectedImage?: Model.Image | null; + gridHeights: number[]; + pageBottom: number; width: number; } export class Root extends React.PureComponent { static displayName = "Root"; + private imageSetRefs: React.RefObject[] = []; + state: State = { + gridHeights: [], + pageBottom: window.innerHeight + window.pageYOffset, width: window.innerWidth }; @@ -27,19 +33,22 @@ export class Root extends React.PureComponent { .then(this._loadHash) .catch(e => console.error("Error fetching data", e)); - window.onresize = () => { - this.setState({ width: window.innerWidth }); - }; + window.onresize = this._onViewChange; + window.onscroll = this._onViewChange; window.onpopstate = this._loadHash; } render() { const imageSets = this.state.data - ? this.state.data.sets.map(set => ( + ? this.state.data.sets.map((set, idx) => ( @@ -82,6 +91,13 @@ export class Root extends React.PureComponent { } }; + private _onViewChange = () => { + this.setState({ + pageBottom: window.innerHeight + window.pageYOffset, + width: window.innerWidth + }); + }; + private _onImageSelected = (img: Model.Image) => { this.setState({ selectedImage: img }); window.history.pushState(null, "", `#${img.src}`); @@ -91,4 +107,19 @@ export class Root extends React.PureComponent { this.setState({ selectedImage: null }); window.history.pushState(null, "", "#"); }; + + private _setGridHeight = (grid: number) => (height: number) => { + if (this.state.gridHeights[grid] === height) { + return; + } + this.setState(state => { + const newGridHeights = [...state.gridHeights]; + newGridHeights[grid] = height; + + return { gridHeights: newGridHeights }; + }); + }; + + private _getPreviousGridHeights = (grid: number): number => + this.state.gridHeights.slice(0, grid).reduce((a, b) => a + b, 0); }