为Tueri.io构建React图像优化组件
Let’s face it, image optimization is hard. We want to make it effortless.
面对现实吧,图像优化非常困难。 我们希望毫不费力。
When we set out to build our React Component there were a few problems we wanted to solve:
当我们开始构建React组件时,我们要解决一些问题:
Automatically decide image width for any device based on the parent container.
根据父容器自动确定任何设备的图像宽度。
Use the best possible image format the user’s browser supports.
使用用户浏览器支持的最佳图像格式。
Automatic image lazy loading.
自动图像延迟加载。
Automatic low-quality image placeholders (LQIP).
自动低质量图像占位符(LQIP)。
Oh, and it had to be effortless for React Developers to use.
哦,React开发人员必须毫不费力地使用它。
这是我们想出的: (This is what we came up with:)
<Img src={ tueriImageId } alt='Alt Text' />
Easy right? Let’s dive in.
容易吧? 让我们潜入。
计算图像尺寸: (Calculating the image size:)
Create a <figure />
element, detect the width and build an image URL:
创建一个<figure />
元素,检测宽度并构建图像URL:
class Img extends React.Component {constructor(props) {super(props)this.state = {width: 0}this.imgRef = React.createRef()}componentDidMount() {const width = this.imgRef.current.clientWidththis.setState({width})}render() {// Destructure props and stateconst { src, alt, options = {}, ext = 'jpg' } = this.propsconst { width } = this.state// Create an empty query stringlet queryString = '' // If width is specified, otherwise use auto-detected widthoptions['w'] = options['w'] || width// Loop through option object and build queryStringObject.keys(options).map((option, i) => {return queryString += `${i < 1 ? '?' : '&'}${option}=${options[option]}`})return(<figure ref={this.imgRef}>{ // If the container width has been set, display the image else nullwidth > 0 ? (<imgsrc={`https://cdn.tueri.io/${ src }/${ kebabCase(alt) }.${ ext }${ queryString }`}alt={ alt }/>) : null }</figure>)}
}export default Img
This returns the following HTML:
这将返回以下HTML:
<figure><img src="https://cdn.tueri.io/tueriImageId/alt-text.jpg?w=autoCalculatedWidth" alt="Alt Text" />
</figure>
使用最佳图像格式: (Use the best possible image format:)
Next, we needed to add support for detecting WebP images and having the Tueri service return the image in the WebP format:
接下来,我们需要添加支持以检测WebP图像并使Tueri服务以WebP格式返回图像:
class Img extends React.Component {constructor(props) {// ...this.window = typeof window !== 'undefined' && windowthis.isWebpSupported = this.isWebpSupported.bind(this)}// ...isWebpSupported() {if (!this.window.createImageBitmap) {return false;}return true;}render() {// ...// If a format has not been specified, detect webp support// Set the fm (format) option in the image URLif (!options['fm'] && this.isWebpSupported) {options['fm'] = 'webp'}// ...return (// ...)}
}// ...
This returns the following HTML:
这将返回以下HTML:
<figure><img src="https://cdn.tueri.io/tueriImageId/alt-text.jpg?w=autoCalculatedWidth&fm=webp" alt="Alt Text" />
</figure>
自动图像延迟加载: (Automatic image lazy loading:)
Now, we need to find out if the <figure />
element is in the viewport, plus we add a little buffer area so the images load just before being scrolled into view.
现在,我们需要确定<figure />
元素是否在视口中,此外,我们还要添加一些缓冲区,以便在滚动到视图之前加载图像。
class Img extends React.Component {constructor(props) {// ...this.state = {// ...isInViewport: falselqipLoaded: false}// ...this.handleViewport = this.handleViewport.bind(this)}componentDidMount() {// ...this.handleViewport()this.window.addEventListener('scroll', this.handleViewport)}handleViewport() {// Only run if the image has not already been loadedif (this.imgRef.current && !this.state.lqipLoaded) {// Get the viewport heightconst windowHeight = this.window.innerHeight// Get the top position of the <figure /> elementconst imageTopPosition = this.imgRef.current.getBoundingClientRect().top// Multiply the viewport * buffer (default buffer: 1.5)const buffer = typeof this.props.buffer === 'number' && this.props.buffer > 1 && this.props.buffer < 10 ? this.props.buffer : 1.5// If <figure /> is in viewportif (windowHeight * buffer > imageTopPosition) {this.setState({isInViewport: true})}}}// ...componentWillUnmount() {this.window.removeEventListener('scroll', this.handleViewport)}render() {// Destructure props and state// ...const { isInViewport, width } = this.state// ...return (<figure ref={this.imgRef}>{ // If the container width has been set, display the image else nullisInViewport && width > 0 ? (<img onLoad={ () => { this.setState({ lqipLoaded: true }) } }// .../>) : null }</figure>)}
}export default Img
自动低质量图像占位符(LQIP): (Automatic low-quality image placeholders (LQIP):)
Finally, when an image is in the viewport, we want to load a 1/10 size blurred image, then fade out the placeholder image when the full-size image is loaded:
最后,当图像在视口中时,我们要加载1/10大小的模糊图像,然后在加载全尺寸图像时淡出占位符图像:
class Img extends React.Component {constructor(props) {// ...this.state = {// ...fullsizeLoaded: false}// ...}// ...render() {// Destructure props and state// ...const { isInViewport, width, fullsizeLoaded } = this.state// ...// Modify the queryString for the LQIP image: replace the width param with a value 1/10 the fullsizeconst lqipQueryString = queryString.replace(`w=${ width }`, `w=${ Math.round(width * 0.1) }`)// Set the default styles. The full size image should be absolutely positioned within the <figure /> elementconst 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 LQIPif (fullsizeLoaded) {styles.lqip.opacity = 0}return(<figurestyle={ styles.figure }// ...>{isInViewport && width > 0 ? (<React.Fragment>{/* Load fullsize image in background */}<img onLoad={ () => { this.setState({ fullsizeLoaded: true }) } }style={ styles.fullsize }src={`https://cdn.tueri.io/${ src }/${ kebabCase(alt) }.${ ext }${ queryString }`}alt={ alt }/>{/* Load LQIP in foreground */}<img onLoad={ () => { this.setState({ lqipLoaded: true }) } }style={ styles.lqip }src={`https://cdn.tueri.io/${ src }/${ kebabCase(alt) }.${ ext }${ lqipQueryString }`} alt={ alt } /></React.Fragment>) : null} </figure>)}
}// ...
放在一起: (Putting it all together:)
Image optimization made effortless. Just swap out your regular <img />
elements for the Tueri <Img />
and never worry about optimization again.
图像优化毫不费力。 只需将您的常规<img />
元素换成Tueri <Img />
,再也不用担心优化。
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.clientWidththis.setState({width})this.handleViewport()this.window.addEventListener('scroll', this.handleViewport)}handleViewport() {if (this.imgRef.current && !this.state.lqipLoaded) {const windowHeight = this.window.innerHeightconst imageTopPosition = this.imgRef.current.getBoundingClientRect().topconst buffer = typeof this.props.buffer === 'number' && this.props.buffer > 1 && this.props.buffer < 10 ? this.props.buffer : 1.5if (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 stateconst { src, alt, options = {}, ext = 'jpg' } = this.propsconst { isInViewport, width, fullsizeLoaded } = this.state// Create an empty query stringlet queryString = ''// If width is specified, otherwise use auto-detected widthoptions['w'] = options['w'] || width// If a format has not been specified, detect webp supportif (!options['fm'] && this.isWebpSupported) {options['fm'] = 'webp'}// Loop through option prop and build queryStringObject.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 fullsizeconst 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 LQIPif (fullsizeLoaded) {styles.lqip.opacity = 0}const missingALt = 'ALT TEXT IS REQUIRED'return(// Return the CDN domain from the TueriProvider<TueriContext.Consumer>{({ domain }) => (<figurestyle={ 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
实际观看: (See it in action:)
Try out a live demo on CodeSandbox:
在CodeSandbox上进行现场演示:
Originally published at Tueri.io
最初发表于Tueri.io
翻译自: https://www.freecodecamp.org/news/building-the-react-image-optimization-component-for-tueri-io/
为Tueri.io构建React图像优化组件相关推荐
- 使用React,TypeScript和Socket.io构建聊天应用
This is going to be a thorough step-by-step guide for building a single page chat application using ...
- mathcal 对应于什么库_如何快速构建React组件库
前言 俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险- 目前团队内已经有较为成熟的 Vue 技术 ...
- react 组件构建_使用React Spring和Tinycolor构建色彩丰富的弹性组件
react 组件构建 Recently, I decided to build a web application to allow designers and developers to gener ...
- react 组件构建_让我们用100行JavaScript构建一个React Chat Room组件
react 组件构建 by Kevin Hsu 通过徐凯文 让我们用100行JavaScript构建一个React Chat Room组件 (Let's Build a React Chat Room ...
- phaser.min.js_如何使用Phaser 3,Express和Socket.IO构建多人纸牌游戏
phaser.min.js I'm a tabletop game developer, and am continually looking for ways to digitize game ex ...
- react-dnd-dom_我如何使用react-dnd和react-flip-move构建React游戏
react-dnd-dom by Nicholas Vincent-Hill 尼古拉斯·文森特·希尔(Nicholas Vincent-Hill) 我如何使用react-dnd和react-flip- ...
- 从0到1,一步步开发React的loading组件,并发布到npm上
没有发布过npm包的同学,可能会对NPM对开发有一种蜜汁敬畏,觉得这是一个很高大上的东西.甚至有次面试,面试官问我有没有发过npm包,当时只用过还没写过,我想应该挺难的,就小声说了没有,然后就让我回去 ...
- react 动态添加组件属性_这么高质量React面试题(含答案),看到就是赚到了!...
前言 本文篇幅较长,全是干货,建议亲们可以先收藏慢慢看哦 写文不易,欢迎大家一起交流,喜欢文章记得关注我点个赞哟,感谢支持! Q1 :什么是虚拟DOM? 难度::star: 虚拟DOM(VDOM)它是 ...
- 使用 create-react-app 构建 react应用程序
2019独角兽企业重金招聘Python工程师标准>>> 本文主要是对SPA搭建的实践过程讲解,在对react.redux.react-router有了初步了解后,来运用这些技术构建一 ...
最新文章
- bootstrap项目更改为vue_取代Jquery,用Vue 构建Bootstrap 4 应用
- jca oracle官方文档,Oracle 官方文档说明
- windows下部署oracle11,windows下配置oracle11g的dataguard
- CSharp 如何OCR离线识别文本
- c++ 怎样连接两个链表_LeetCode | 链表的入口,一文帮你搞定“环形链表”(python版,最简单解析)...
- total是什么牌子的电脑_干货!如何用Python在笔记本电脑上分析100GB数据(上)...
- 论文阅读《Vision-Aided RAIM: A New Method for GPS Integrity Monitoring in Approach and Landing Phase》3
- 磁共振线圈分类_磁共振线圈的发展历程
- Minecraft 1.12.2模组开发(四十五) 水火两用船
- 计算机组装与维护doc,新版计算机组装与维护.doc
- rar,zip文件加密判断
- vue项目导出word文件(根据word模板导出)
- iphone的Safari浏览器中HTML5上传图片方向问题解决方法
- 狂欢,不过是一群人的孤单--来自人人
- android 刷机 zip,安卓自制zip刷机包 zip包刷机脚本函数详细例举教程
- 联发科mtk和骁龙730哪个好_联发科g90t和骁龙730哪个好? 配置、跑分对比
- osg 三维gis开发_GIS+BIM跨界融合应用:智能管理道路“健康”,精准定位路桥病害...
- 【DVB】DVB-T系统的参数和搜台介绍
- 谷歌浏览器油猴插件(Tampermonkey)安装使用教程
- linux下杀死进程