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图像优化组件相关推荐

  1. 使用React,TypeScript和Socket.io构建聊天应用

    This is going to be a thorough step-by-step guide for building a single page chat application using ...

  2. mathcal 对应于什么库_如何快速构建React组件库

    前言 俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险- 目前团队内已经有较为成熟的 Vue 技术 ...

  3. react 组件构建_使用React Spring和Tinycolor构建色彩丰富的弹性组件

    react 组件构建 Recently, I decided to build a web application to allow designers and developers to gener ...

  4. react 组件构建_让我们用100行JavaScript构建一个React Chat Room组件

    react 组件构建 by Kevin Hsu 通过徐凯文 让我们用100行JavaScript构建一个React Chat Room组件 (Let's Build a React Chat Room ...

  5. 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 ...

  6. react-dnd-dom_我如何使用react-dnd和react-flip-move构建React游戏

    react-dnd-dom by Nicholas Vincent-Hill 尼古拉斯·文森特·希尔(Nicholas Vincent-Hill) 我如何使用react-dnd和react-flip- ...

  7. 从0到1,一步步开发React的loading组件,并发布到npm上

    没有发布过npm包的同学,可能会对NPM对开发有一种蜜汁敬畏,觉得这是一个很高大上的东西.甚至有次面试,面试官问我有没有发过npm包,当时只用过还没写过,我想应该挺难的,就小声说了没有,然后就让我回去 ...

  8. react 动态添加组件属性_这么高质量React面试题(含答案),看到就是赚到了!...

    前言 本文篇幅较长,全是干货,建议亲们可以先收藏慢慢看哦 写文不易,欢迎大家一起交流,喜欢文章记得关注我点个赞哟,感谢支持! Q1 :什么是虚拟DOM? 难度::star: 虚拟DOM(VDOM)它是 ...

  9. 使用 create-react-app 构建 react应用程序

    2019独角兽企业重金招聘Python工程师标准>>> 本文主要是对SPA搭建的实践过程讲解,在对react.redux.react-router有了初步了解后,来运用这些技术构建一 ...

最新文章

  1. bootstrap项目更改为vue_取代Jquery,用Vue 构建Bootstrap 4 应用
  2. jca oracle官方文档,Oracle 官方文档说明
  3. windows下部署oracle11,windows下配置oracle11g的dataguard
  4. CSharp 如何OCR离线识别文本
  5. c++ 怎样连接两个链表_LeetCode | 链表的入口,一文帮你搞定“环形链表”(python版,最简单解析)...
  6. total是什么牌子的电脑_干货!如何用Python在笔记本电脑上分析100GB数据(上)...
  7. 论文阅读《Vision-Aided RAIM: A New Method for GPS Integrity Monitoring in Approach and Landing Phase》3
  8. 磁共振线圈分类_磁共振线圈的发展历程
  9. Minecraft 1.12.2模组开发(四十五) 水火两用船
  10. 计算机组装与维护doc,新版计算机组装与维护.doc
  11. rar,zip文件加密判断
  12. vue项目导出word文件(根据word模板导出)
  13. iphone的Safari浏览器中HTML5上传图片方向问题解决方法
  14. 狂欢,不过是一群人的孤单--来自人人
  15. android 刷机 zip,安卓自制zip刷机包 zip包刷机脚本函数详细例举教程
  16. 联发科mtk和骁龙730哪个好_联发科g90t和骁龙730哪个好? 配置、跑分对比
  17. osg 三维gis开发_GIS+BIM跨界融合应用:智能管理道路“健康”,精准定位路桥病害...
  18. 【DVB】DVB-T系统的参数和搜台介绍
  19. 谷歌浏览器油猴插件(Tampermonkey)安装使用教程
  20. linux下杀死进程

热门文章

  1. 已成功拿下字节、腾讯、脉脉offer,算法太TM重要了
  2. 终于有人把安卓程序员必学知识点全整理出来了,BAT大厂面试总结
  3. Struts2框架使用(十)之struts2的上传和下载
  4. 通过Xshell登录远程服务器实时查看log日志
  5. JavaScript覆盖率统计实现
  6. 分析不同类型页面渲染过程
  7. nginx编译安装时添加echo模块
  8. 01-gt;选中UITableViewCell后,Cell中的UILabel的背景颜色变成透明色
  9. 29. ExtJs - Struts2 整合(1) - 登录页面
  10. 数据库笔记2:SQL运算符