Skip to main content

React Image Placeholder

ยท 7 min read
Pere Pages
Pear being loaded

The rationale behind creating a component like the ImagePlaceholder in React, especially in a web application that handles a significant amount of images, centers on improving user experience and performance.

Key reasonsโ€‹

  1. Enhanced User Experience: Displaying a placeholder with the image's dimensions provides immediate feedback to users about the space that the image will occupy once loaded. This can prevent layout shifts, a common issue that occurs when images load and push content around, which can be disorienting for users.

  2. Perceived Performance: By showing something (like a placeholder) while the image is loading, the application feels more responsive. Even though it doesn't technically load the image faster, it improves the perceived performance, making the user experience smoother.

  3. Avoiding Blank Spaces: Without a placeholder, areas designated for images might appear as blank spaces until the images load, especially in cases of slow internet connections. A placeholder can indicate that more content is on the way.

  4. Informative: Displaying the image dimensions (or any other placeholder content) can inform users about the expected size and aspect ratio of the image.

  5. Customization and Styling: A custom component allows for more flexibility in terms of styling and behavior. For example, you could add animations, custom loading indicators, or error states if an image fails to load.

  6. Network Performance Optimization: In some implementations, you can use placeholders to defer the loading of actual images, which can be beneficial for optimizing network usage, especially for users on limited data plans or slow connections.

  7. SEO and Accessibility: A well-implemented image placeholder can also contribute to better SEO and accessibility. For example, including appropriate alt text and other attributes can make your images more accessible to screen readers and search engines.

Use Casesโ€‹

  • Image-Heavy Websites: Websites like online stores, galleries, or news portals, where the user's first interaction might be with a page full of unloaded images.
  • Slow Connection Scenarios: Applications targeting areas with slower internet connections where load times can be significantly improved with placeholders.
  • Responsive Web Design: To maintain layout integrity across different screen sizes and prevent layout shifts as images load.

SSR (server side rendering)โ€‹

When using Server-Side Rendering (SSR) with a component like ImagePlaceholder, you might encounter issues because SSR environments don't have access to browser-specific features like the window object, document object, or loading of images in the same way a client's browser does. Here's why this might be causing issues with your component:

  1. Browser-Specific APIs: The ImagePlaceholder component relies on the browser's image loading mechanism (the onLoad event of the img tag). In SSR, the code is executed on the server where these APIs are not available. Therefore, the image loading logic will not work as expected.

  2. State Changes and Rehydration: The component's state change from isLoaded = false to isLoaded = true upon image load relies on client-side JavaScript. In SSR, the initial HTML sent to the client will always have isLoaded = false. When the client-side JavaScript runs (rehydration), discrepancies between the server-rendered HTML and the expected client-side DOM can lead to issues.

Solutionsโ€‹

  1. Client-Side Only Loading:

    • Ensure that the image loading logic is executed only on the client side. You can use a flag that checks if the code is running on the server or the client. For example, you can use typeof window === 'undefined' to check if the code is running in a server environment.
  2. Placeholder Fallback for SSR:

    • Provide a fallback for the SSR. For instance, always render the placeholder on the server. Then, let the client-side code replace it with the actual image when it runs.
  3. No-Script Fallback:

    • Provide a fallback for users who have JavaScript disabled. This can be a simple noscript tag that displays the image normally.

loading="lazy"โ€‹

The <img loading="lazy"> attribute is designed to work independently of CSS styles, including display: none;. Here's how it breaks down:

  1. Lazy Loading Mechanism: The loading="lazy" attribute on an <img> tag tells the browser to delay loading the image until it's about to enter the viewport. This is primarily used to improve page load times and reduce bandwidth usage for users by only loading images as they're needed.

  2. CSS display: none;: When you apply display: none; to an image, it removes the image from the document layout entirely. Visually, the image won't be rendered, but it still exists in the HTML document.

  3. Interaction Between Lazy Loading and display: none;: If an image is set to loading="lazy" and also styled with display: none;, the browser will still consider it for lazy loading. However, because the image isn't taking up any space in the document (due to display: none;), its loading behavior might be affected in terms of when it's considered to be near the viewport.

    • Visibility for Loading: For a lazy-loaded image to start loading, the browser needs to anticipate when it will enter the viewport. If the image is hidden with display: none;, it won't be loaded until the style changes to make it potentially visible (e.g., changing to display: block;) and it's near the viewport based on the scroll position.
  4. Practical Use: Using loading="lazy" on an image that is initially hidden (display: none;) can be practical in scenarios where the image will later be shown (like in image carousels or tabs that aren't initially visible). When the image's display property changes to make it visible, and if it's near or within the viewport, the browser will then load the image if it hasn't already.

So, in summary, while loading="lazy" works with display: none;, the lazy loading only triggers based on the potential to enter the viewport, which requires changing the display property to make the image visible or potentially visible in the layout.

Codeโ€‹

In the ImagePlaceholder component, the most crucial part of the code is the logic that manages the loading state of the image and switches between displaying the placeholder and the actual image. This functionality is central to the component's purpose, which is to enhance user experience by providing feedback while an image is loading.

Here are the key parts of this logic:

  1. State Management for Image Loading

    • The use of the useState hook to track whether the image has finished loading is vital. This state (isLoaded) determines whether the placeholder or the actual image is displayed.
    const [isLoaded, setIsLoaded] = useState(false);
  2. Image Load Event Handling

    • The onLoad event handler on the img tag is critical. It sets the isLoaded state to true when the image has finished loading, triggering a re-render to display the actual image.
    <img loading="lazy"
    src={src}
    alt={alt}
    style={imageStyle}
    onLoad={handleImageLoaded}
    width={width}
    height={height}
    />
  3. Conditional Rendering

    • The logic that conditionally renders either the placeholder or the image based on the isLoaded state is a key part of fulfilling the component's purpose.
    return (
    <>
    <div style={placeholderStyle}>{`${width} x ${height}`}</div>
    <img loading="lazy"
    // ... attributes
    />
    </>
    );
  4. Styling for Consistent Layout

    • The styling applied to ensure that the placeholder and the image have the same dimensions is important for preventing layout shifts during image loading.

These elements work together to ensure that the component behaves as intended: showing a placeholder with the image's dimensions until the image is fully loaded, then displaying the image without causing unexpected layout changes.

ImagePlaceholder.js
const isSSR = typeof window === "undefined";

const ImagePlaceholder = ({ src, alt, options }) => {
const [isLoaded, setIsLoaded] = useState(isSSR);

return (
<>
{isLoaded ? null : (
<div className="image-placeholder-common image-placeholder">
<span className="icon">๐Ÿž๏ธ</span>
{alt}
</div>
)}
<img
loading="lazy"
className="image-placeholder-common"
style={
isLoaded
? { display: "inherit" }
: { position: "absolute", width: "5px", height: "5px", zIndex: -1000 }
}
src={src}
alt={alt}
onLoad={() => setIsLoaded(true)}
/>
</>
);
};

Tipโ€‹

When developing components like ImagePlaceholder, consider the context in which they will be used. For instance, if your application heavily relies on visual content, investing time in creating well-designed placeholders can significantly enhance user experience. Also, keep accessibility in mind; ensure that your placeholders and images are accessible to all users, including those using assistive technologies.