next-optimized-images
next-optimized-images copied to clipboard
How would one dynamically inline an image based on a prop?
Here is my Image wrapper. I would like to inline above-the-fold images so they don't pop in. I can't pass the prop in - it doesn't work.
Here's the structure I have:
import { AspectRatio } from 'react-aspect-ratio'
export default function Image({ alt = "decorative image", inline = 'false', lazy = true, src, divClasses, ratio = 1, ...rest }) {
const imageName = src.slice(src.lastIndexOf('/'), src.lastIndexOf('.'))
const extension = src.slice(src.lastIndexOf('.'))
// slicing extension is for webpack compatibility
return (
<div className={divClasses} style={{ lineHeight: '0px' }}>
<picture>
<source srcSet={require(`../public/images${imageName}${extension}?webp`)} type="image/webp" />
<source srcSet={require(`../public/images${imageName}${extension}`)} type={extension == '.jpg' || extension == '.jpeg' ? 'image/jpeg' : `image/${extension.slice(1)}`} />
<AspectRatio ratio={ratio}>
<img src={require(`../public/images${imageName}${extension}`)} alt={alt} {...rest} />
</AspectRatio>
</picture>
</div>
)
}
Here's what I would like to have:
import { AspectRatio } from 'react-aspect-ratio'
export default function Image({ alt = "decorative image", inline = 'false', lazy = true, src, divClasses, ratio = 1, ...rest }) {
const imageName = src.slice(src.lastIndexOf('/'), src.lastIndexOf('.'))
const extension = src.slice(src.lastIndexOf('.'))
// slicing extension is for webpack compatibility
return (
<div className={divClasses} style={{ lineHeight: '0px' }}>
<picture>
<source srcSet={require(`../public/images${imageName}${extension}?webp${inline && '&inline'}`)} type="image/webp" />
<source srcSet={require(`../public/images${imageName}${extension}${inline && '?inline'}`)} type={extension == '.jpg' || extension == '.jpeg' ? 'image/jpeg' : `image/${extension.slice(1)}`} />
<AspectRatio ratio={ratio}>
<img src={require(`../public/images${imageName}${extension}${inline && '?inline'}`)} alt={alt} {...rest} />
</AspectRatio>
</picture>
</div>
)
}
Same problem. Any solution?
You need to use babel compiler for replace param of image at the place of the component call
babel-plugin-replace-path-images.js
var babelParser = require('@babel/parser')
const nestedVisitor = {
StringLiteral(path, opts) {
try{
var pathFile = path.node.value;
if(pathFile && /\_\_load\_images\_\_/g.test(pathFile)){
console.log('optimize image', pathFile)
var newPath = pathFile.replace('__load_images__', ''),
originPath = newPath;
var pathRequest = newPath.match(/\?.*/g);
if(pathRequest){
pathRequest = pathRequest[0];
newPath = newPath.replace(pathRequest, '')
}
if( !newPath ) return match;
var resizeMode = pathRequest && /\\?resize/.test(pathRequest),
webpMode = pathRequest && /\\?webp/.test(pathRequest),
images = [], imagesString = '[';
if(resizeMode){
images.push(`require('public/static/${newPath}?resize&format=webp')`)
if(/\.png/g.test(newPath)){
images.push(`require('public/static/${newPath}?resize&format=png')`);
}
if(/\.jpg/g.test(newPath)){
images.push(`require('public/static/${newPath}?resize&format=jpg')`);
}
}
if(webpMode){
images.push(`{path: require('public/static/${newPath}?webp')}`)
images.push(`{path: require('public/static/${newPath}')}`)
}
if(!webpMode && !resizeMode){
images.push(`{path: require('public/static/${originPath}')}`)
}
for(var item of images){
imagesString += item + ','
}
imagesString += ']';
var node = babelParser.parse(imagesString)
path.replaceWith(
node.program.body[0]
);
}
}catch(err){
console.log('err', err)
}
}
}
const nestedVisitor2 = {
JSXExpressionContainer(path, opts){
path.traverse(nestedVisitor, opts);
}
}
module.exports = function({ types: t }) {
return {
visitor: {
JSXAttribute(path) {
path.traverse(nestedVisitor2, t);
}
}
};
}
then apply plugin in babel.config.js
require('dotenv').config();
var DEV_VAR = process.env.DEV_VAR == 'true' ? false : true;
module.exports = function (api) {
api.cache(DEV_VAR);
return {
presets: [
["next/babel"]
],
"plugins": [
["./babel-plugin-replace-path-images.js"]
]
}
}
It also works correctly on devices that do not support the webp image format.
My realization of image wrapper component -
import { Component } from 'react';
class Img extends Component {
componentDidMount() {
if (this.img.complete) {
this.img.style.background = 'initial';
}
}
render() {
return(
<img
ref={el => this.img = el}
onLoad={ e => {
e.target.style.background = 'initial'
}}
{...this.props}
/>
)
}
}
const Image = (props) => {
if(!props.src) return null;
var newProps = JSON.parse(JSON.stringify(props))
var images = [], image, placeholder, addExtra = false;
for(var item of props.src){
if(item.images){
if(!addExtra){
addExtra = true;
image = item.image;
placeholder = item.placeholder;
}
for(var item2 of item.images){
images.push(item2)
}
}else{
images.push(item)
}
}
var reversed = images.reverse();
delete newProps.src;
delete newProps.require;
delete newProps.priority;
if(placeholder){
newProps.style = {background: `url(${placeholder})`}
}
if(!props.priority){
newProps.loading = "lazy"
}
return(
<picture>
{reversed.map(item => {
var sourceProps = {
srcSet: item.path,
type: 'image/'
}
if(/(\.png|data\:image\/png\;base64)/g.test(item.path)){
sourceProps.type += 'png'
}else if(/(\.webp|data\:image\/webp\;base64)/g.test(item.path)){
sourceProps.type += 'webp'
}else if(/(\.jpg|data\:image\/jpg\;base64)/g.test(item.path)){
sourceProps.type += 'jpeg'
}else{
delete sourceProps.type;
}
if(item.width){
sourceProps.media = `(min-width: ${item.width - 10}px)`
}
return(
<source {...sourceProps} />
)
})}
<Img
src={image}
{...newProps}
/>
</picture>
)
};
export default Image;
finally example how you can use it
<Image draggable="false" src={"__load_images__happyDeveloper.png?resize"} alt="Счастливый разработчик" />
is there a easy solution to this ? the answer is not so clear to me