Improvements
- Use Picture component for focus view - remove hover animation - bigger rows - avoid dangling, too-tall images - update node modules
This commit is contained in:
@@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mount"><noscript>I don't like javascript, either.</noscript></div>
|
<div id="mount"><noscript>I don't like javascript, either.</noscript></div>
|
||||||
<script type="text/javascript" src="dist/bundle.js"></script>
|
<script type="text/javascript" src="bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
9603
package-lock.json
generated
9603
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,10 +22,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
|
"esbuild": "^0.8.50",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.4",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^5.66.0",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^4.9.1",
|
||||||
"webpack-dev-server": "^3.11.0"
|
"webpack-dev-server": "^4.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
site.css
29
site.css
@@ -35,6 +35,16 @@ h2 {
|
|||||||
border-bottom: 1px solid #eef;
|
border-bottom: 1px solid #eef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ImageSet-location,
|
||||||
|
.ImageSet-description {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ImageSet-location:after {
|
||||||
|
content: " · ";
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
.Grid {
|
.Grid {
|
||||||
margin-bottom: 45px;
|
margin-bottom: 45px;
|
||||||
}
|
}
|
||||||
@@ -46,42 +56,27 @@ h2 {
|
|||||||
|
|
||||||
.Grid img {
|
.Grid img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-duration: .05s;
|
|
||||||
transition-property: transform, box-shadow;
|
|
||||||
transition-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Grid img:hover {
|
|
||||||
box-shadow: 1px 2px 5px rgba(0, 0, 0, .7);
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.BigPicture {
|
.BigPicture {
|
||||||
|
align-items: center;
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 30px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.BigPicture-image {
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.BigPicture-footer {
|
.BigPicture-footer {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 30px;
|
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as Model from "../model";
|
import * as Model from "../model";
|
||||||
|
import { Picture } from "./picture";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
@@ -6,6 +7,7 @@ export interface Props {
|
|||||||
image: Model.Image;
|
image: Model.Image;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BigPicture extends React.PureComponent<Props, {}> {
|
export class BigPicture extends React.PureComponent<Props, {}> {
|
||||||
@@ -20,14 +22,17 @@ export class BigPicture extends React.PureComponent<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const src = `img/1600/${this.props.image.src}`;
|
const scaleWidth = this.props.image.width / this.props.width;
|
||||||
|
const scaleHeight = this.props.image.height / (this.props.height - 80);
|
||||||
|
const scale = Math.max(scaleWidth, scaleHeight);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="BigPicture">
|
<div className="BigPicture">
|
||||||
<div
|
<Picture
|
||||||
className="BigPicture-image"
|
image={this.props.image}
|
||||||
style={{
|
onClick={() => {}}
|
||||||
backgroundImage: `url(${src})`
|
height={this.props.image.height / scale}
|
||||||
}}
|
width={this.props.image.width / scale}
|
||||||
/>
|
/>
|
||||||
<div className="BigPicture-footer">
|
<div className="BigPicture-footer">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ export interface Props {
|
|||||||
onImageSelected: (image: Model.Image) => void;
|
onImageSelected: (image: Model.Image) => void;
|
||||||
pageBottom: number;
|
pageBottom: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ROW_HEIGHT = 200;
|
export const ROW_HEIGHT = 250;
|
||||||
export const MOBILE_ROW_HEIGHT = 100;
|
export const MOBILE_ROW_HEIGHT = 150;
|
||||||
|
|
||||||
interface Row {
|
interface Row {
|
||||||
images: Model.Image[];
|
images: Model.Image[];
|
||||||
@@ -53,7 +54,7 @@ export class Grid extends React.PureComponent<Props, {}> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const images = rows.map(row => {
|
const images = rows.map(row => {
|
||||||
const height = this.props.width / row.width;
|
const height = Math.min(this.props.height, this.props.width / row.width);
|
||||||
|
|
||||||
const pics = row.images.map(image => {
|
const pics = row.images.map(image => {
|
||||||
return (
|
return (
|
||||||
@@ -61,6 +62,7 @@ export class Grid extends React.PureComponent<Props, {}> {
|
|||||||
image={image}
|
image={image}
|
||||||
onClick={() => this.props.onImageSelected(image)}
|
onClick={() => this.props.onImageSelected(image)}
|
||||||
key={image.src}
|
key={image.src}
|
||||||
|
height={height}
|
||||||
width={(image.width / image.height) * height}
|
width={(image.width / image.height) * height}
|
||||||
defer={this.gridHeight > this.props.pageBottom}
|
defer={this.gridHeight > this.props.pageBottom}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface Props {
|
|||||||
setGridHeight: (height: number) => void;
|
setGridHeight: (height: number) => void;
|
||||||
pageBottom: number;
|
pageBottom: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImageSet extends React.PureComponent<Props, {}> {
|
export class ImageSet extends React.PureComponent<Props, {}> {
|
||||||
@@ -20,13 +21,15 @@ export class ImageSet extends React.PureComponent<Props, {}> {
|
|||||||
return (
|
return (
|
||||||
<div className="ImageSet" ref={this.divRef}>
|
<div className="ImageSet" ref={this.divRef}>
|
||||||
<h2>
|
<h2>
|
||||||
{this.props.imageSet.location} · {this.props.imageSet.description}
|
<span className="ImageSet-location">{this.props.imageSet.location}</span>
|
||||||
|
<span className="ImageSet-description">{this.props.imageSet.description}</span>
|
||||||
</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}
|
pageBottom={this.props.pageBottom}
|
||||||
width={this.props.width}
|
width={this.props.width}
|
||||||
|
height={this.props.height}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as React from "react";
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
image: Model.Image;
|
image: Model.Image;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
defer?: boolean;
|
defer?: boolean;
|
||||||
}
|
}
|
||||||
@@ -47,8 +48,10 @@ export class Picture extends React.PureComponent<Props, State> {
|
|||||||
<source srcSet={srcSet.webp} type="image/webp" />
|
<source srcSet={srcSet.webp} type="image/webp" />
|
||||||
<source srcSet={srcSet.jpeg} type="image/jpeg" />
|
<source srcSet={srcSet.jpeg} type="image/jpeg" />
|
||||||
<img
|
<img
|
||||||
|
id={this.props.image.src}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
src={srcSet.bestSrc}
|
src={srcSet.bestSrc}
|
||||||
|
height={this.props.height + "px"}
|
||||||
width={this.props.width + "px"}
|
width={this.props.width + "px"}
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface State {
|
|||||||
gridHeights: number[];
|
gridHeights: number[];
|
||||||
pageBottom: number;
|
pageBottom: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Root extends React.PureComponent<Props, State> {
|
export class Root extends React.PureComponent<Props, State> {
|
||||||
@@ -31,11 +32,24 @@ export class Root extends React.PureComponent<Props, State> {
|
|||||||
document.getElementById("mount")!.clientWidth
|
document.getElementById("mount")!.clientWidth
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
private _viewHeight = (): number => {
|
||||||
|
// iOS Safari does not set outerWidth/outerHeight
|
||||||
|
if (!window.outerHeight) {
|
||||||
|
return window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(
|
||||||
|
window.innerHeight,
|
||||||
|
window.outerHeight,
|
||||||
|
document.body.clientHeight
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
gridHeights: [],
|
gridHeights: [],
|
||||||
pageBottom: window.innerHeight + window.pageYOffset,
|
pageBottom: window.innerHeight + window.pageYOffset,
|
||||||
width: this._viewWidth()
|
width: this._viewWidth(),
|
||||||
|
height: this._viewHeight()
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -65,6 +79,7 @@ export class Root extends React.PureComponent<Props, State> {
|
|||||||
setGridHeight={this._setGridHeight(idx)}
|
setGridHeight={this._setGridHeight(idx)}
|
||||||
onImageSelected={this._onImageSelected}
|
onImageSelected={this._onImageSelected}
|
||||||
width={this.state.width}
|
width={this.state.width}
|
||||||
|
height={this.state.height}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null;
|
: null;
|
||||||
@@ -72,7 +87,7 @@ export class Root extends React.PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className="Root">
|
<div className="Root">
|
||||||
{this._bigPicture()}
|
{this._bigPicture()}
|
||||||
<h1>Ski</h1>
|
<h1>Aaron's Ski Pictures</h1>
|
||||||
{imageSets}
|
{imageSets}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -84,6 +99,7 @@ export class Root extends React.PureComponent<Props, State> {
|
|||||||
image={this.state.selectedImage}
|
image={this.state.selectedImage}
|
||||||
onClose={this._showGrid}
|
onClose={this._showGrid}
|
||||||
width={this.state.width}
|
width={this.state.width}
|
||||||
|
height={this.state.height}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@@ -108,7 +124,8 @@ export class Root extends React.PureComponent<Props, State> {
|
|||||||
private _onViewChange = () => {
|
private _onViewChange = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
pageBottom: window.innerHeight + window.pageYOffset,
|
pageBottom: window.innerHeight + window.pageYOffset,
|
||||||
width: this._viewWidth()
|
width: this._viewWidth(),
|
||||||
|
height: this._viewHeight(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<title>Skiing - Aaron Gutierrez</title>
|
<title>Skiing – Aaron Gutierrez</title>
|
||||||
<link rel="stylesheet" href="{0}">
|
<link rel="stylesheet" href="{0}">
|
||||||
<meta charshet="utf-8">
|
<meta charshet="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|||||||
@@ -7,16 +7,21 @@ module.exports = (env) => {
|
|||||||
entry: "./src/index.tsx",
|
entry: "./src/index.tsx",
|
||||||
output: {
|
output: {
|
||||||
filename: "bundle.js",
|
filename: "bundle.js",
|
||||||
path: __dirname + "/dist"
|
path: path.join(__dirname, "dist")
|
||||||
},
|
},
|
||||||
|
|
||||||
mode: mode,
|
mode: mode,
|
||||||
|
|
||||||
// Enable sourcemaps for debugging webpack's output.
|
// Enable sourcemaps for debugging webpack's output.
|
||||||
devtool: "source-map",
|
devtool: "source-map",
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
|
},
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
publicPath: "/dist/",
|
static: {
|
||||||
|
directory: __dirname,
|
||||||
|
},
|
||||||
port: 8080
|
port: 8080
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user