defer image loading

This commit is contained in:
2018-12-29 11:23:40 -08:00
parent c6eb48ca87
commit 7487364571
4 changed files with 101 additions and 16 deletions

View File

@@ -6,6 +6,7 @@ import * as React from "react";
export interface Props { export interface Props {
images: Model.Image[]; images: Model.Image[];
onImageSelected: (image: Model.Image) => void; onImageSelected: (image: Model.Image) => void;
pageBottom: number;
width: number; width: number;
} }
@@ -20,7 +21,11 @@ interface Row {
export class Grid extends React.PureComponent<Props, {}> { export class Grid extends React.PureComponent<Props, {}> {
static displayName = "Grid"; static displayName = "Grid";
private gridHeight = 0;
render() { render() {
this.gridHeight = 0;
let row: Model.Image[] = []; let row: Model.Image[] = [];
const rows: Row[] = []; const rows: Row[] = [];
let rowWidth = 0; let rowWidth = 0;
@@ -57,9 +62,15 @@ export class Grid extends React.PureComponent<Props, {}> {
onClick={() => this.props.onImageSelected(image)} onClick={() => this.props.onImageSelected(image)}
key={image.src} key={image.src}
width={(image.width / image.height) * height} width={(image.width / image.height) * height}
defer={
this.gridHeight > this.props.pageBottom + 2 * this._rowHeight()
}
/> />
); );
}); });
this.gridHeight += height;
return ( return (
<div <div
className="Grid-row" className="Grid-row"

View File

@@ -6,24 +6,43 @@ import * as React from "react";
export interface Props { export interface Props {
imageSet: Model.ImageSet; imageSet: Model.ImageSet;
onImageSelected: (img: Model.Image) => void; onImageSelected: (img: Model.Image) => void;
setGridHeight: (height: number) => void;
pageBottom: number;
width: number; width: number;
} }
export class ImageSet extends React.PureComponent<Props, {}> { export class ImageSet extends React.PureComponent<Props, {}> {
static displayName = "ImageSet"; static displayName = "ImageSet";
private divRef: React.RefObject<HTMLDivElement> = React.createRef();
render() { render() {
return ( return (
<div className="ImageSet"> <div className="ImageSet" ref={this.divRef}>
<h2> <h2>
{this.props.imageSet.location} · {this.props.imageSet.description} {this.props.imageSet.location} · {this.props.imageSet.description}
</h2> </h2>
<Grid <Grid
images={this.props.imageSet.images} images={this.props.imageSet.images}
onImageSelected={this.props.onImageSelected} onImageSelected={this.props.onImageSelected}
pageBottom={this.props.pageBottom}
width={this.props.width} width={this.props.width}
/> />
</div> </div>
); );
} }
componentDidMount() {
this._setGridHeight();
}
componentDidUpdate() {
this._setGridHeight();
}
private _setGridHeight = () => {
if (this.divRef.current) {
this.props.setGridHeight(this.divRef.current.clientHeight);
}
};
} }

View File

@@ -6,19 +6,43 @@ export interface Props {
image: Model.Image; image: Model.Image;
onClick: () => void; onClick: () => void;
width: number; width: number;
defer?: boolean;
}
export interface State {
hasLoadedOnce: boolean;
} }
interface SrcSetInfo { interface SrcSetInfo {
srcSet: string; srcSet: string;
bestSrc: string; bestSrc: string;
} }
export class Picture extends React.PureComponent<Props, {}> { export class Picture extends React.PureComponent<Props, State> {
static displayName = "Picture"; static displayName = "Picture";
state: State = {
hasLoadedOnce: false
};
static getDerivedStateFromProps(props: Props, state: State): State | null {
return !state.hasLoadedOnce && !props.defer
? { hasLoadedOnce: true }
: null;
}
render() { render() {
if (this.props.defer && !this.state.hasLoadedOnce) {
return (
<div
className="Picture-defer"
style={{ width: this.props.width + "px" }}
/>
);
}
const srcSet = this._srcset(); const srcSet = this._srcset();
return ( return (
<img <img
onClick={this.props.onClick} onClick={this.props.onClick}
@@ -44,18 +68,18 @@ export class Picture extends React.PureComponent<Props, {}> {
if (scale >= 1) { if (scale >= 1) {
srcs.push(`img/${size}/${this.props.image.src} ${scale}x`); srcs.push(`img/${size}/${this.props.image.src} ${scale}x`);
if (scale < bestRatio) { if (scale < bestRatio) {
bestSize = size; bestSize = size;
bestRatio = scale; bestRatio = scale;
} }
} }
}); });
srcs.push(`img/${bestSize}/${this.props.image.src} 1x`); srcs.push(`img/${bestSize}/${this.props.image.src} 1x`);
return { return {
srcSet: srcs.join(","), srcSet: srcs.join(","),
bestSrc: `img/${bestSize}/${this.props.image.src}` bestSrc: `img/${bestSize}/${this.props.image.src}`
}; };
}; };
} }

View File

@@ -9,13 +9,19 @@ export interface Props {}
export interface State { export interface State {
data?: Model.Data | null; data?: Model.Data | null;
selectedImage?: Model.Image | null; selectedImage?: Model.Image | null;
gridHeights: number[];
pageBottom: number;
width: number; width: number;
} }
export class Root extends React.PureComponent<Props, State> { export class Root extends React.PureComponent<Props, State> {
static displayName = "Root"; static displayName = "Root";
private imageSetRefs: React.RefObject<ImageSet>[] = [];
state: State = { state: State = {
gridHeights: [],
pageBottom: window.innerHeight + window.pageYOffset,
width: window.innerWidth width: window.innerWidth
}; };
@@ -27,19 +33,22 @@ export class Root extends React.PureComponent<Props, State> {
.then(this._loadHash) .then(this._loadHash)
.catch(e => console.error("Error fetching data", e)); .catch(e => console.error("Error fetching data", e));
window.onresize = () => { window.onresize = this._onViewChange;
this.setState({ width: window.innerWidth }); window.onscroll = this._onViewChange;
};
window.onpopstate = this._loadHash; window.onpopstate = this._loadHash;
} }
render() { render() {
const imageSets = this.state.data const imageSets = this.state.data
? this.state.data.sets.map(set => ( ? this.state.data.sets.map((set, idx) => (
<ImageSet <ImageSet
key={set.location + set.description} key={set.location + set.description}
imageSet={set} imageSet={set}
pageBottom={
this.state.pageBottom - this._getPreviousGridHeights(idx)
}
setGridHeight={this._setGridHeight(idx)}
onImageSelected={this._onImageSelected} onImageSelected={this._onImageSelected}
width={this.state.width} width={this.state.width}
/> />
@@ -82,6 +91,13 @@ export class Root extends React.PureComponent<Props, State> {
} }
}; };
private _onViewChange = () => {
this.setState({
pageBottom: window.innerHeight + window.pageYOffset,
width: window.innerWidth
});
};
private _onImageSelected = (img: Model.Image) => { private _onImageSelected = (img: Model.Image) => {
this.setState({ selectedImage: img }); this.setState({ selectedImage: img });
window.history.pushState(null, "", `#${img.src}`); window.history.pushState(null, "", `#${img.src}`);
@@ -91,4 +107,19 @@ export class Root extends React.PureComponent<Props, State> {
this.setState({ selectedImage: null }); this.setState({ selectedImage: null });
window.history.pushState(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);
} }