Is it possible to lazy load background images with react-lazyload - javascript

I have created a Section component which will take in an image as a property and its children as content to be displayed within the section, so the component would look as follows...
<Section image={'path/to/image'}>
//content
</Section>
The component will take the image property and set it as a url for background-image style...
let sectionStyle = {
backgroundImage: `url(${this.props.image})`
}
which will then be processed in the return element...
return (
<section
style={this.props.image ? sectionStyle : null}>
<div>
{this.props.children}
</div>
</section>
)
My question is, is it possible to Lazyload the background image whilst also not compromising the contents availability for SEO? in other words i want to avoid LazyLoading the entire Section, but somehow LazyLoad just the image associated with the Section.

An updated version of #naoise-golden 's answer
import PropTypes from 'prop-types';
import React from 'react';
export default class LazyImage extends React.Component {
constructor (props) {
super(props);
this.state = {
src: null,
};
}
componentDidMount () {
const { src } = this.props;
console.log('LazyImage componentDidMount props:', this.props);
const imageLoader = new Image();
imageLoader.src = src;
imageLoader.onload = () => {
console.log('LazyImage loaded src:', src);
this.setState({ src });
};
}
render () {
const { placeholder, className, height, width, alt } = this.props;
const { src } = this.state;
return (
<img src={src || placeholder} className={className} height={height} width={width} alt={alt} />
);
}
}
LazyImage.propTypes = {
src: PropTypes.string,
placeholder: PropTypes.string,
className: PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
alt: PropTypes.string,
};

In order to defer loading background-image, you will need to create a stylesheet for the CSS properties that load any file with url, since you don't want these images to delay the first contentful paint.
For instance:
FirstModal.module.less
This file has the vital CSS properties that will be loaded first...
.styles {
&-container {
position: absolute;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
align-items: center;
}
}
firstModalBackground.module.less
This file will load after the critical CSS...
.styles {
&-container {
background: url('../../images/code/code.jpg') no-repeat center center fixed;
background-size: cover;
}
}
For demonstration purposes, I will use React.Component here, but, if you want to try to optimize things, you can also use React.PureComponent (I tested it and everything worked fine).
firstModal.jsx
const classNames = require('classnames');
const React = require('react)';
const {stylesContainer} = require('./firstModal.module.less');
class FirstModal extends React.Component {
constructor(props) {
super(props);
this.state = {
classNames: classNames([stylesContainer])
};
}
async componentDidMount() {
const backgroundImage = await import(
'./firstModalBackground.module.less'
);
this.setState({
classNames: [
classNames(this.state.classNames, [backgroundImage.stylesContainer]),
]
});
}
render() {
// console.log(this.state.classNames);
return <div className={this.state.classNames}>It werks!</div>;
}
}
module.exports = FirstModal;
You could even take a step further, if you have a low resolution image that loads faster, you can have a "three-step background-image loading", for instance, on componentDidMount:
async componentDidMount() {
const lowResolutionBackgroundImage = await import(
'../greeting-page/firstModalLowResBackground.module.less'
);
const baseClass = this.state.classNames;
this.setState({
classNames: [
classNames(baseclass,
lowResolutionBackgroundImage.stylesContainer),
]
});
const backgroundImage = await import(
'./firstModalBackground.module.less'
);
this.setState({
classNames: [
classNames(baseClass,
backgroundImage.stylesContainer),
]
});
}

Here is a simple component to lazy load images:
class LazyImage extends React.Component {
state = { src: null };
componentDidMount() {
const { src } = this.props;
const imageLoader = new Image();
imageLoader.src = src;
imageLoader.onload = () => {
this.setState({ src });
};
}
render() {
return <img src={this.state.src || this.props.placeholder} />;
}
}
You would call it with <LazyImage src='path/to/hd.jpg' placeholder='path/to/placeholder.jpg' />

I created a library for lazy-loading images. It's main feature is to provide dynamic resizing of images, but it can also solve your problem. I recently PR'd in some changes for background image lazy loading.
https://github.com/peterlazzarino/react-adaptive-image
Here's an example you could use for lazy background image loading that will resolve images from imgur. In my production app my image resolver points at an image server, but this is totally customize-able. You will just need to style the .header-image class to have a height and width.
import React from 'react';
import ReactDOM from 'react-dom';
import { initImages } from 'react-adaptive-image';
import AdaptiveImage from 'react-adaptive-image';
initImages({
imageResolver: function(image){
return `https://i.imgur.com/${image.fileName}`
}
})
class App extends React.Component {
render() {
return (
<AdaptiveImage backgroundImage className="header-image" fileName={'U6zWkcZ.png'} />
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));

Related

Apply changes to the HTML strings when it comes from props in React?

I'm trying to add pre-loader icon before rendering an image in React component using the below code:
export default class MediaPostItem extends BaseComponent {
public constructor(props: IMediaPostItemProperties) {
super(props);
this.state = {
imageIsReady: false,
};
}
componentDidMount(): void {
const img = new Image();
img.onload = () => this.setState({ imageIsReady: true });
img.src = this.props.item.featuredImage?.node.sourceUrl;
}
public render(): ReactNode {
const { item } = this.props;
const { imageIsReady } = this.state;
return(
{imageIsReady ? (
<img src={item.featuredImage?.node.sourceUrl}/>
) : (<div> Loading</div>)
})
}
}
Now in the other component I give a HTML strings as a real HTML in a react component using the below code:
<div dangerouslySetInnerHTML={{ __html: props.content }}></div>
The content is including some <img> tags and what I need is to add a pre-loader for these img tags too.
I'm not sure if it's possible or not. Is there any way to apply changes to the HTML strings when it comes from props?
You can do that with ReactDOM.render.
import { render } from "react-dom";
const htmlString = `
<div>
<div id="toBeReplaced"/></div>
</div>
`;
class ReplacedComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>Replaced Component</h1>;
}
}
export default class Parent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
render(
<React.StrictMode>
<ReplacedComponent />
</React.StrictMode>,
document.getElementById("toBeReplaced")
);
}
render() {
return <div dangerouslySetInnerHTML={{ __html: htmlString }}></div>;
}
}
You would need to select the <img> tags, with some attribute (class/id) then loop over them and render your component.

How to use leader-line (an external javascript library) in react?

I want to use leader-line in my React web project. It is an external javascript library, but I don't know how to integrate it into the project with the JSX syntax.
For example, its documentation tells us the general implementation:
Html
<div id="start">start</div>
<div id="end">end</div>
Javascript
// Add new leader line from `start` to `end` (HTML/SVG elements, basically).
new LeaderLine(
document.getElementById('start'),
document.getElementById('end')
);
How should I write in JSX file?
I try to write below, but failed.
import React, { Component } from 'react';
import LeaderLine from 'leader-line'
class Page extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));
}
render() {
return (
<div className="Page">
<div id="start"></div>
<div id="end"></div>
</div>
)
}
}
export default Page;
This is the npm package page of leader-line.
Depending on what you are trying to achieve with leader-line, you may find that you can achieve it just as well with react-xarrows.
https://www.npmjs.com/package/react-xarrows
React-xarrows can be integrated into a React app much more easily (even using DOM identifiers rather than React Refs, if you prefer).
See this example code (taken directly from the link above), showing usage.
import React, { useRef } from "react";
import Xarrow from "react-xarrows";
const boxStyle = {
border: "grey solid 2px",
borderRadius: "10px",
padding: "5px",
};
function SimpleExample() {
const box1Ref = useRef(null);
return (
<div
style={{ display: "flex", justifyContent: "space-evenly", width: "100%" }}
>
<div ref={box1Ref} style={boxStyle}>
hey
</div>
<p id="elem2" style={boxStyle}>
hey2
</p>
<Xarrow
start={box1Ref} //can be react ref
end="elem2" //or an id
/>
</div>
);
}
I've made a small prototype to illustrate how it could be achieved.
class Line extends React.Component {
componentDidMount () {
this.waitWhenRefIsReady();
// scroll and resize listeners could be assigned here
}
componentWillUnmount () {
if(this.timer) {
clearInterval(this.timer);
}
}
shouldComponentUpdate () {
setTimeout(() => {
// skip current even loop and wait
// the end of parent's render call
if(this.line) {
this.line.position();
}
}, 0);
// you should disable react render at all
return false;
}
waitWhenRefIsReady () {
// refs are generated via mutations - wait for them
this.timer = setInterval(() => {
if(this.props.start.current) {
clearInterval(this.timer);
this.drawLine();
}
}, 5);
}
drawLine () {
const {start, end} = this.props;
this.line = new LeaderLine(start.current, end.current);
}
render () {
return null;
}
}
class App extends React.Component {
constructor (props) {
super(props);
this.state = {
left: 0,
};
this.myRef1 = React.createRef();
this.myRef2 = React.createRef();
}
componentDidMount() {
this.animateLine();
}
animateLine() {
setInterval(() => {
const limit = 200;
const {left} = this.state;
const x = ((left % limit) + limit) % limit;
this.setState({left: x + 10});
}, 1000);
}
render () {
const {left} = this.state;
const {myRef1, myRef2} = this;
return <div className="container">
<Line
start={this.myRef1}
end={this.myRef2} />
<div
id="start"
ref={this.myRef1}
style={{
left: `${left}px`
}}></div>
<div
id="end"
ref={this.myRef2}></div>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Leader Line + React JSX simple prototype
import LeaderLine from 'leader-line';
Add in leader-line.min.js (at end)
if (module && module.exports) { module.exports = LeaderLine }
Refer to this thread for how to integrate Leaderline into your react project :
https://github.com/anseki/leader-line/issues/8#issuecomment-370147614
in summary,
you cant just do
import LeaderLine from 'leader-line';
to import LeaderLine, because its not an ES2015 module yet!
similar to how #shilpa pointed out,
you can tweak the webpack config to include -
rules: [
{
test: require('path').resolve(__dirname, 'node_modules/leader-line/'),
use: [{
loader: 'skeleton-loader',
options: {procedure: content => `${content}export default LeaderLine`}
}]
}
and then inside componentDidMount, you could do
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));

Reactjs - Passing Color as Prop

I'm trying to use a color as a prop in a reactjs setting. In the General_heading component, I want to be able to pass a prop to define the color of the heading, but I can't get it to respond currently. Thoughts?
in app.js:
<div><General_header theme_color="red"/></div>
in General_header.js:
import React, { Component } from 'react';
class General_header extends React.Component {
constructor(props) {
super(props);
this.state = {logo_img: props.logo_img,
theme_color: props.theme_color};
}
render() {
return (
<div style={[styles.header,{backgroundColor:this.state.theme_color}]}>test
<img src={this.state.logo_img} alt={'logo'} style={styles.logo_img} />
</div>
);
}
}
var styles = {
header: {
height: '100px',
display: 'block'},
logo_img: {
height: '40px',
display: 'inline-block'}
}
export default General_header;
Use camelCase
Have a look at this https://github.com/airbnb/javascript/tree/master/react#naming
<GeneralHeader themeColor="red"/>
constructor(props) {
super(props);
this.state = {
logoImg: props.logoImg,
themeColor: props.themeColor
};
}
<div style={{backgroundColor: this.state.themeColor}}>
Made some changes below to your code:
class General_header extends React.Component {
render() {
// This will create a single object containing fields
// from styles.header plus the extra backgroundColor field.
const fullStyle = {
...styles.header, // This is called the spread operator
backgroundColor: this.props.theme_color // No need to assign this to state
}
return (
<div style={fullStyle}>test
<img src={this.state.logo_img} alt={'logo'} style={styles.logo_img} />
</div>
);
}
}
ES5 version:
var fullStyle = Object.assign(
{},
styles.header,
{ backgroundColor: this.props.theme_color }
);

Conditional Rendering not reliably setting state on different components

I have two navigation buttons (light version, and dark version) that I want to render on certain pages.
I tried setting the state in the constructor, and generating the link to the images based on the path of the page, but sometimes the wrong link to the image will generated. It seems as though it's getting the state based on the first page that was ever generated. For example, if "home" is supposed to have the light version of the button any other link I click will generate the light version of the logo, unless I refresh. If "about" is supposed to have the dark version of the logo, all other pages I click through will have the dark version, unless I refresh.
Why won't it generate properly while naturally clicking around and navigating through the different pages?
MenuButton.js
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
componentDidMount() {
let currentPath = window.location.pathname;
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
} else {
this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
}
}
render() {
return (
<div className="menu-btn--cntr">
<img src={this.state.logo_url} />
</div>
)
}
}
You don't need to use state and life cycle.
You can try something like below -
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
getButton() {
let currentPath = window.location.pathname;
let btnUrl = ''; // or set some default
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
// this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
} else {
// this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
}
return btnUrl;
}
render() {
const btnUrl = this.getButton();
return (
<div className="menu-btn--cntr">
<img src={btnUrl} />
</div>
)
}
}

Get dimensions of image with React

I need to get the dimensions of an image with React. I found a library called react-measure that computes measurements of React components. It works, but I can't get it to fire when the image has loaded. I need to get it to fire when the image loads so I get accurate dimensions and not 0 x 157 or something like that.
I tried using the onLoad Image Event to detect when the image has loaded, but I didn't get satisfactory results. Essentially what I've done is when the image has loaded (handleImageLoaded() has been called), change the hasLoaded state property to true. I know for a fact that hasLoaded has been changed to true because it says so: Image Loaded: true.
What I noticed is that I can calculate the dimensions for only images that have been cached...
Here is a demo video: cl.ly/250M2g3X1k21
Is there a better, more concise way of retrieving dimensions properly with React?
Here is the code:
import React, {Component} from 'react';
import Measure from '../src/react-measure';
class AtomicImage extends Component {
constructor() {
super();
this.state = {
hasLoaded: false,
dimensions: {}
};
this.onMeasure = this.onMeasure.bind(this);
this.handleImageLoaded = this.handleImageLoaded.bind(this);
}
onMeasure(dimensions) {
this.setState({dimensions});
}
handleImageLoaded() {
this.setState({hasLoaded: true});
}
render() {
const {src} = this.props;
const {hasLoaded, dimensions} = this.state;
const {width, height} = dimensions;
return(
<div>
<p>Dimensions: {width} x {height}</p>
<p>Image Loaded: {hasLoaded ? 'true' : 'false'}</p>
<Measure onMeasure={this.onMeasure} shouldMeasure={hasLoaded === true}>
<div style={{display: 'inline-block'}}>
<img src={src} onLoad={this.handleImageLoaded}/>
</div>
</Measure>
</div>
);
}
}
export default AtomicImage;
Here is the parent code. It's not really important—just passes src to the AtomicImage element:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import AtomicImage from './AtomicImage';
class App extends Component {
constructor() {
super();
this.state = {src: ''}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(e) {
this.setState({src: e.target.value});
}
render() {
const {src} = this.state;
return (
<div>
<div>
<input onChange={this.handleOnChange} type="text"/>
</div>
<AtomicImage src={src} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
way of retrieving dimensions
You can achieve your goal just by js: through offsetHeight, offsetWidth.
In order to get the img's dimensions, img must be visible. You can't get dimensions from a cached img.
example: https://jsbin.com/jibujocawi/1/edit?js,output
class AtomicImage extends Component {
constructor(props) {
super(props);
this.state = {dimensions: {}};
this.onImgLoad = this.onImgLoad.bind(this);
}
onImgLoad({target:img}) {
this.setState({dimensions:{height:img.offsetHeight,
width:img.offsetWidth}});
}
render(){
const {src} = this.props;
const {width, height} = this.state.dimensions;
return (<div>
dimensions width{width}, height{height}
<img onLoad={this.onImgLoad} src={src}/>
</div>
);
}
}
The accepted answer is wrong for me. I'm getting the correct result with this for the onImgLoad() method:
noteImgSize(img){
this.imgOrigH=img.nativeEvent.srcElement.naturalHeight
this.imgOrigW=img.nativeEvent.srcElement.naturalWidth
this.imgOrigRatio=this.imgOrigH/this.imgOrigW
}
Side Note, unrelated to this:
Use Mobx and never have to call setState() ever again!

Categories