How to reserve space for responsive img (stop blocks jumping down)

Viewed 519

How to solve a problem of jumping down blocks after image load if image should be responsive?

Codesandbox.

Info:

  • Image should shrink on window resize
  • Image max-width is it's own width
  • We don't know image size
  • JS can be used

Idea:

If it's not possible without any image size data, then can it be done only with image aspect ratio?

If you know image aspect ratio, you can calculate block height and put responsive image inside with some unused space (at least unused space will be not big on small window size).

Image loading: enter image description here

Image loaded: enter image description here

Html:

import React from "react";
import "./styles.css";

const img =
  "https://s3.amazonaws.com/images.seroundtable.com/google-css-images-1515761601.jpg";

export default function App() {
  return (
    <div className="app">
      <img alt="" src={img + `?${Date.now()}`} className="img" />
      <h1>I always jump down :(</h1>
    </div>
  );
}

CSS:

.app {
  box-sizing: border-box;
}
.img {
  max-width: 100%;
}
h1 {
  background-color: red;
}
3 Answers

AS @DBS sad, it seems that only solution is to wrap image in div block with calculated height based on window.width and aspect ratio (in my case i know it). If there is any better solutions, don't hesitate to answer.

Sandbox solution example.

import React from "react";
import "./styles.css";
import styled from "styled-components";

const aspectRatio = [1, 5];

const img =
  "https://s3.amazonaws.com/images.seroundtable.com/google-css-images-1515761601.jpg";

export default function App() {
  return (
    <div className="app">
      <StyledDiv>
        <img alt="" src={img + `?${Date.now()}`} className="img" />
      </StyledDiv>

      <h1>I always jump down :(</h1>
    </div>
  );
}

const StyledDiv = styled.div`
  height: calc(100vw / ${aspectRatio[1]});
  overflow-y: hidden;
`;

As you've already determined, even when knowing the aspect ratio, we still have the problem of not knowing the width of the image, making the aspect ratio useless if we are to conform to your requirement Image max-width is it's own width.

What would work is a build time solution (if you know your image urls at build time). There are plenty of server side rendering options for react apps and I will not elaborate on them.

Instead there's a super rudimentary DIY approach.

  1. Create a Node script to run on your server. It will build JSON file with image dimensions. Let's call it "buildImageDimensionData.js". It will output a JSON file imageDimensions.json.
var probe = require("probe-image-size"); // For getting image dimensions without downloading full image
const fs = require("fs"); // For writing our image dimensions to file

// Supply the image urls by hand, or build a webpack script to extract them etc...
const imageUrls = [
  `http://localhost:5000/image1.jpg`,
  `http://localhost:5000/image2.jpg`,
];

async function getImageDimensionsByUrl(urlList) {
  const probePromises = urlList.map(async (imageUrl) => await probe(imageUrl));
  let probedImagesData = [];

  try {
    probedImagesData = await Promise.all(probePromises);
  } catch (error) {
    // We are programming gods and there will be no error.
  }

  return probedImagesData.reduce((accumulator, imageData) => {
    accumulator[imageData.url] = [imageData.width, imageData.height];
    return accumulator;
  }, {});
}

getImageDimensionsByUrl(imageUrls).then((dimensions) =>
  fs.writeFileSync("imageDimensions.json", JSON.stringify(dimensions))
);
  1. Run the script before build. For example, if you're using Create React App, in package.json:
    ...
    "start": "buildImageDimensionData && react-scripts start",
    "build": "buildImageDimensionData && react-scripts build",
    ...
  1. In your react image component, import imageDimensions.json and use the data to set dimensions for image wrapper based on url of the image.

Note this is more for demonstraing how it could work and there will definitely be packages for doing this better.


Another, albeit poor option is of course just waiting for the images to load before rendering your other content so no jumps occur.

To ignore the jump down, you have to set margin of the next sibling to 0,

.app {
  box-sizing: border-box;
}
.img {
  max-width: 100%;
}
.app :nth-child(2) {
margin:0px
}

Related