Created
March 19, 2019 16:17
-
-
Save dane-stevens/43765b2b5963cb3dd1bb4e27463f8af0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import PropTypes from 'prop-types' | |
import { TueriContext } from './Provider' | |
import kebabCase from 'lodash.kebabcase' | |
class Img extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
isInViewport: false, | |
width: 0, | |
height: 0, | |
lqipLoaded: false, | |
fullsizeLoaded: false | |
} | |
this.imgRef = React.createRef() | |
this.window = typeof window !== 'undefined' && window | |
this.handleViewport = this.handleViewport.bind(this) | |
this.isWebpSupported = this.isWebpSupported.bind(this) | |
} | |
componentDidMount() { | |
const width = this.imgRef.current.clientWidth | |
this.setState({ | |
width | |
}) | |
this.handleViewport() | |
this.window.addEventListener('scroll', this.handleViewport) | |
} | |
handleViewport() { | |
if (this.imgRef.current && !this.state.lqipLoaded) { | |
const windowHeight = this.window.innerHeight | |
const imageTopPosition = this.imgRef.current.getBoundingClientRect().top | |
const buffer = typeof this.props.buffer === 'number' && this.props.buffer > 1 && this.props.buffer < 10 ? this.props.buffer : 1.5 | |
if (windowHeight * buffer > imageTopPosition) { | |
this.setState({ | |
isInViewport: true | |
}) | |
} | |
} | |
} | |
isWebpSupported() { | |
if (!this.window.createImageBitmap) { | |
return false; | |
} | |
return true; | |
} | |
componentWillUnmount() { | |
this.window.removeEventListener('scroll', this.handleViewport) | |
} | |
render() { | |
// Destructure props and state | |
const { src, alt, options = {}, ext = 'jpg' } = this.props | |
const { isInViewport, width, fullsizeLoaded } = this.state | |
// Create an empty query string | |
let queryString = '' | |
// If width is specified, otherwise use auto-detected width | |
options['w'] = options['w'] || width | |
// If a format has not been specified, detect webp support | |
if (!options['fm'] && this.isWebpSupported) { | |
options['fm'] = 'webp' | |
} | |
// Loop through option prop and build queryString | |
Object.keys(options).map((option, i) => { | |
return queryString += `${i < 1 ? '?' : '&'}${option}=${options[option]}` | |
}) | |
// Modify the queryString for the LQIP image: replace the width param with a value 1/10 the fullsize | |
const lqipQueryString = queryString.replace(`w=${ width }`, `w=${ Math.round(width * 0.1) }`) | |
const styles = { | |
figure: { | |
position: 'relative', | |
margin: 0 | |
}, | |
lqip: { | |
width: '100%', | |
filter: 'blur(5px)', | |
opacity: 1, | |
transition: 'all 0.5s ease-in' | |
}, | |
fullsize: { | |
position: 'absolute', | |
top: '0px', | |
left: '0px', | |
transition: 'all 0.5s ease-in' | |
} | |
} | |
// When the fullsize image is loaded, fade out the LQIP | |
if (fullsizeLoaded) { | |
styles.lqip.opacity = 0 | |
} | |
const missingALt = 'ALT TEXT IS REQUIRED' | |
return( | |
// Return the CDN domain from the TueriProvider | |
<TueriContext.Consumer> | |
{({ domain }) => ( | |
<figure | |
style={ styles.figure } | |
ref={this.imgRef} | |
> | |
{ | |
// | |
isInViewport && width > 0 ? ( | |
<React.Fragment> | |
{/* Load fullsize image in background */} | |
<img | |
onLoad={ () => { this.setState({ fullsizeLoaded: true }) } } | |
style={ styles.fullsize } | |
src={`${ domain }/${ src }/${ kebabCase(alt || missingALt) }.${ ext }${ queryString }`} | |
alt={ alt || missingALt } | |
/> | |
{/* Load LQIP in foreground */} | |
<img | |
onLoad={ () => { this.setState({ lqipLoaded: true }) } } | |
style={ styles.lqip } | |
src={`${ domain }/${ src }/${ kebabCase(alt || missingALt) }.${ ext }${ lqipQueryString }`} | |
alt={ alt || missingALt } | |
/> | |
</React.Fragment> | |
) : null | |
} | |
</figure> | |
)} | |
</TueriContext.Consumer> | |
) | |
} | |
} | |
Img.propTypes = { | |
src: PropTypes.string.isRequired, | |
alt: PropTypes.string.isRequired, | |
options: PropTypes.object, | |
ext: PropTypes.string, | |
buffer: PropTypes.number | |
} | |
export default Img |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment