defer image loading
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user