2016-10-25 17:01:37 +13:00
|
|
|
|
|
|
|
|
import React, { PropTypes } from 'react'
|
|
|
|
|
|
|
|
|
|
export default class Parallax extends React.Component{
|
|
|
|
|
|
2017-10-10 13:05:28 +13:00
|
|
|
constructor(props){
|
2016-10-25 17:01:37 +13:00
|
|
|
super(props);
|
|
|
|
|
|
2016-11-28 08:24:01 +13:00
|
|
|
this._loading = false
|
2016-11-28 09:07:18 +13:00
|
|
|
this._loaded = false
|
2016-11-28 08:24:01 +13:00
|
|
|
|
2016-10-25 17:01:37 +13:00
|
|
|
this.state = {
|
2016-10-25 21:36:33 +13:00
|
|
|
scrollTop: 0,
|
|
|
|
|
windowWidth: 0,
|
|
|
|
|
windowHeight: 0,
|
|
|
|
|
canvas: {
|
|
|
|
|
width: 0,
|
|
|
|
|
height: 0
|
|
|
|
|
},
|
2016-10-26 08:51:28 +13:00
|
|
|
image: {},
|
2016-11-28 08:24:01 +13:00
|
|
|
loading: false,
|
2016-11-04 09:35:39 +13:00
|
|
|
url: false
|
2016-10-25 17:01:37 +13:00
|
|
|
}
|
2016-10-26 16:55:47 +13:00
|
|
|
|
|
|
|
|
// we need to manually bind this as eventListener doesn't work with anonymous functions
|
|
|
|
|
this.handleResize = this.handleResize.bind(this);
|
2016-11-04 09:35:39 +13:00
|
|
|
this.handleScroll = this.handleScroll.bind(this);
|
2016-10-25 17:01:37 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount(){
|
2018-04-11 16:44:41 +12:00
|
|
|
this._mounted = true;
|
2016-10-26 16:55:47 +13:00
|
|
|
window.addEventListener("resize", this.handleResize);
|
2016-11-04 09:35:39 +13:00
|
|
|
window.addEventListener("scroll", this.handleScroll);
|
2016-11-29 15:36:01 +13:00
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
if (this.props.image){
|
2018-04-11 16:44:41 +12:00
|
|
|
this._loading = true;
|
|
|
|
|
this.setState({url: this.props.image, image: false, loading: true});
|
|
|
|
|
this.loadImage(this.props.image)
|
2016-11-29 15:36:01 +13:00
|
|
|
.then(
|
|
|
|
|
response => {
|
2017-10-10 13:05:28 +13:00
|
|
|
if (this._mounted){
|
2018-04-11 16:44:41 +12:00
|
|
|
this._loading = false;
|
|
|
|
|
this._loaded = true;
|
|
|
|
|
this.setState({url: this.props.image, image: response, loading: false});
|
|
|
|
|
this.updateCanvas(response);
|
2016-11-29 15:36:01 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
2017-07-24 09:09:13 +12:00
|
|
|
} else {
|
2018-04-11 16:44:41 +12:00
|
|
|
this._loaded = true;
|
|
|
|
|
this.state.image = false;
|
|
|
|
|
this.updateCanvas();
|
2016-11-29 15:36:01 +13:00
|
|
|
}
|
2016-10-25 21:36:33 +13:00
|
|
|
}
|
|
|
|
|
|
2016-11-04 09:35:39 +13:00
|
|
|
componentWillUnmount(){
|
2018-04-11 16:44:41 +12:00
|
|
|
this._mounted = false;
|
2016-10-26 16:55:47 +13:00
|
|
|
window.removeEventListener("resize", this.handleResize);
|
2016-11-04 09:35:39 +13:00
|
|
|
window.removeEventListener("scroll", this.handleScroll);
|
2016-10-26 16:55:47 +13:00
|
|
|
}
|
|
|
|
|
|
2017-10-10 13:05:28 +13:00
|
|
|
componentWillReceiveProps(nextProps){
|
|
|
|
|
if ((!this.state.url || nextProps.image != this.state.url ) && !this._loading && nextProps.image){
|
2017-10-27 21:36:50 +13:00
|
|
|
this._loading = true;
|
|
|
|
|
this.setState({ url: nextProps.image, image: false, loading: true });
|
|
|
|
|
this.loadImage(nextProps.image)
|
2016-11-23 13:17:07 +13:00
|
|
|
.then(
|
|
|
|
|
response => {
|
2017-10-10 13:05:28 +13:00
|
|
|
if (this._mounted){
|
2017-10-27 21:36:50 +13:00
|
|
|
this._loading = false;
|
|
|
|
|
this._loaded = true;
|
|
|
|
|
this.setState({ url: nextProps.image, image: response, loading: false });
|
|
|
|
|
this.updateCanvas(response);
|
2016-11-28 08:24:01 +13:00
|
|
|
}
|
2016-11-23 13:17:07 +13:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
2016-11-10 08:08:03 +13:00
|
|
|
}
|
|
|
|
|
|
2016-11-04 09:35:39 +13:00
|
|
|
handleResize(e){
|
2017-07-24 09:09:13 +12:00
|
|
|
if (this._loaded){
|
2018-04-11 16:44:41 +12:00
|
|
|
this.updateCanvas(this.state.image);
|
2016-11-28 09:07:18 +13:00
|
|
|
}
|
2016-10-26 16:55:47 +13:00
|
|
|
}
|
|
|
|
|
|
2016-11-04 09:35:39 +13:00
|
|
|
handleScroll(e){
|
2016-11-06 15:47:37 +13:00
|
|
|
// this DOES work, but is in no way high-performing and only on Firefox
|
2017-07-24 09:09:13 +12:00
|
|
|
if (this._loaded){
|
2016-11-28 09:07:18 +13:00
|
|
|
this.setState(
|
2018-04-11 16:44:41 +12:00
|
|
|
{scrollTop: window.scrollY},
|
2017-07-24 09:09:13 +12:00
|
|
|
this.updateCanvas(this.state.image)
|
2016-11-28 09:07:18 +13:00
|
|
|
)
|
|
|
|
|
}
|
2016-11-04 09:35:39 +13:00
|
|
|
}
|
|
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
loadImage(url){
|
2017-10-10 13:05:28 +13:00
|
|
|
return new Promise((resolve, reject) => {
|
2016-10-25 21:36:33 +13:00
|
|
|
|
2017-10-27 21:36:50 +13:00
|
|
|
var imageObject = new Image();
|
|
|
|
|
imageObject.src = url;
|
|
|
|
|
|
|
|
|
|
// This seems to prevent Dirble images from loading.
|
|
|
|
|
// Other domains don't seem to mind any more, so perhaps we can remove for good.
|
|
|
|
|
// TODO: Re-test the nature of crossorigin images
|
|
|
|
|
// imageObject.crossOrigin = 'anonymous';
|
2016-10-25 21:36:33 +13:00
|
|
|
|
|
|
|
|
imageObject.onload = function(){
|
2016-10-26 08:51:28 +13:00
|
|
|
var image = {
|
|
|
|
|
width: imageObject.naturalWidth,
|
|
|
|
|
height: imageObject.naturalHeight,
|
|
|
|
|
original_width: imageObject.naturalWidth,
|
|
|
|
|
original_height: imageObject.naturalHeight,
|
|
|
|
|
object: imageObject
|
|
|
|
|
}
|
2017-10-27 21:36:50 +13:00
|
|
|
resolve(image);
|
2016-10-25 21:36:33 +13:00
|
|
|
}
|
2016-10-25 17:01:37 +13:00
|
|
|
})
|
2016-10-25 21:36:33 +13:00
|
|
|
}
|
|
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
updateCanvas(image = false){
|
2016-10-25 21:36:33 +13:00
|
|
|
var canvasWidth = $('.parallax').outerWidth();
|
|
|
|
|
var canvasHeight = $('.parallax').outerHeight();
|
2017-07-24 09:09:13 +12:00
|
|
|
if (this.state.canvas.width != canvasWidth || this.state.canvas.height != canvasHeight){
|
2016-10-25 21:36:33 +13:00
|
|
|
this.setState({
|
|
|
|
|
canvas: {
|
|
|
|
|
width: canvasWidth,
|
|
|
|
|
height: canvasHeight
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2018-04-11 16:44:41 +12:00
|
|
|
this.renderCanvas(image);
|
2016-10-25 17:01:37 +13:00
|
|
|
}
|
|
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
renderCanvas(image = false){
|
|
|
|
|
let self = this;
|
|
|
|
|
var canvasDOM = document.getElementById('parallax-canvas')
|
|
|
|
|
var context = canvasDOM.getContext('2d')
|
2017-01-19 08:05:20 +13:00
|
|
|
|
2017-07-25 09:22:28 +12:00
|
|
|
// Fill the background with mid-grey
|
2018-04-11 16:44:41 +12:00
|
|
|
context.beginPath();
|
|
|
|
|
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
2018-08-27 08:21:41 +12:00
|
|
|
|
|
|
|
|
switch (this.props.theme){
|
|
|
|
|
case 'light':
|
|
|
|
|
context.fillStyle = "#EDEDED";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'dark':
|
|
|
|
|
default:
|
|
|
|
|
context.fillStyle = "#121212";
|
|
|
|
|
}
|
2018-04-11 16:44:41 +12:00
|
|
|
context.fill();
|
2016-10-25 21:36:33 +13:00
|
|
|
|
2018-04-11 16:44:41 +12:00
|
|
|
if (image && !self.state.loading){
|
2016-10-25 21:36:33 +13:00
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
// zoom image to fill canvas, widthwise
|
2017-10-10 13:05:28 +13:00
|
|
|
if (image.width < this.state.canvas.width || image.width > this.state.canvas.width){
|
2017-07-24 09:09:13 +12:00
|
|
|
var scale = this.state.canvas.width / image.width;
|
|
|
|
|
image.width = image.width * scale;
|
|
|
|
|
image.height = image.height * scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now check for fill heightwise, and zoom in if necessary
|
2017-10-10 13:05:28 +13:00
|
|
|
if (image.height < this.state.canvas.height){
|
2017-07-24 09:09:13 +12:00
|
|
|
var scale = this.state.canvas.height / image.height;
|
|
|
|
|
image.width = image.width * scale;
|
|
|
|
|
image.height = image.height * scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// figure out where we want the image to be, based on scroll position
|
2017-10-10 13:05:28 +13:00
|
|
|
var percent = Math.round(self.state.scrollTop / this.state.canvas.height * 100 );
|
|
|
|
|
var position = Math.round((this.state.canvas.height / 2) * (percent/100) ) - 100;
|
2017-07-24 09:09:13 +12:00
|
|
|
|
2017-10-10 13:05:28 +13:00
|
|
|
image.x = (this.state.canvas.width / 2 ) - (image.width / 2 );
|
|
|
|
|
image.y = ((this.state.canvas.height / 2 ) - (image.height / 2 ) ) + ((percent / 100 ) * 100);
|
2017-07-24 09:09:13 +12:00
|
|
|
|
|
|
|
|
// Actually draw the image on the canvas
|
2018-02-16 16:58:46 +13:00
|
|
|
context.drawImage(image.object, image.x, image.y, image.width, image.height);
|
2017-07-24 09:09:13 +12:00
|
|
|
|
|
|
|
|
// now update our component
|
|
|
|
|
self.setState({ image: image });
|
2016-10-25 21:36:33 +13:00
|
|
|
}
|
|
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
// Construct a gradient overlay
|
2018-02-16 16:58:46 +13:00
|
|
|
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
|
|
|
|
let gradient = context.createLinearGradient(0, 0, 0, this.state.canvas.height);
|
2018-08-27 08:21:41 +12:00
|
|
|
|
|
|
|
|
switch (this.props.theme){
|
|
|
|
|
case 'light':
|
2018-08-27 08:56:39 +12:00
|
|
|
gradient.addColorStop(0, 'rgba(255,255,255,0.4)');
|
|
|
|
|
gradient.addColorStop(0.9, 'rgba(255,255,255,1)');
|
2018-08-27 08:21:41 +12:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'dark':
|
|
|
|
|
default:
|
|
|
|
|
gradient.addColorStop(0, 'rgba(24,24,24,0)');
|
|
|
|
|
gradient.addColorStop(0.9, 'rgba(24,24,24,1)');
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 16:58:46 +13:00
|
|
|
context.fillStyle = gradient;
|
2016-10-25 21:36:33 +13:00
|
|
|
|
2017-07-24 09:09:13 +12:00
|
|
|
// And now drop it into place
|
2018-02-16 16:58:46 +13:00
|
|
|
context.fill();
|
2016-10-25 17:01:37 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render(){
|
|
|
|
|
return (
|
2017-05-26 09:04:58 +12:00
|
|
|
<div className={this.props.blur ? "parallax blur" : "parallax"}>
|
|
|
|
|
|
2016-10-26 16:55:47 +13:00
|
|
|
<canvas
|
|
|
|
|
id="parallax-canvas"
|
2017-05-26 09:04:58 +12:00
|
|
|
className={!this.state.loading ? 'loaded' : null}
|
2016-10-26 16:55:47 +13:00
|
|
|
width={this.state.canvas.width}
|
|
|
|
|
height={this.state.canvas.height}>
|
|
|
|
|
</canvas>
|
2016-10-25 17:01:37 +13:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2017-05-26 09:04:58 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|