0

This is a tricky issue and may not be easy to solve.

The issue is, for our current system, the images / photos are very big, such as 5MB or 6MB each, and uploaded by different teams every few days. Even though we can ask the backend team to have a system to convert them into thumbnail and smaller images later, such as 50kb or 150kb, it may still be a few months away and is beyond our control. (Another point is, when we let the user preview these photos in a modal, we may want to give them the full size image instead of a compressed one).

If we have vanilla JavaScript to load a second image, such as in an image preview modal on the webpage, when the user clicks on "Toggle" to see the next image (and Toggle again to see the next image), there are two issues:

  1. the title and image mismatch with each other

  2. the user cannot know that the image is being loaded. It is as if there is no reaction after clicking

The code is:

        document
          .querySelector("#toggle-button")
          .addEventListener("click", () => {
            toggle = !toggle;
            filename = `${toggle ? "beach.jpg" : "mountain.jpg"}?${Date.now()}`;
            elMsg.innerHTML = `<span>Title is ${
              toggle ? "Beach" : "Mountain and Lake"
            }</span>`;
            elImage.src = filename;
          });

and you can try the code here:
https://codesandbox.io/s/silly-haze-4uonqv?file=/index.html

To make the effect more obvious, please go to DevTool on Google Chrome, and then Network Tab, and change the "No Throttling" to "Slow 3G" speed.

Then you will see issue #1 and issue #2 mentioned above.

Note that here, I am using a Date.now() to attach to the filename, so that the image URL is:

https://4uonqv.csb.app/beach.jpg?1678392429912

so as to bust the cache, for demo purpose to show the two issues.

It is the same if I use React, with the code:

function App() {
  const [toggle, setToggle] = useState(true);

  const imageFilename = `${
    toggle ? "beach.jpg" : "mountain.jpg"
  }?${Date.now()}`;

  return (
    <div className="App">
      <div id="msg">
        <span>toggle is {JSON.stringify(toggle)}</span>
        <span>imageFilename is {imageFilename}</span>
        <span>Title is {toggle ? "Beach" : "Mountain and Lake"}</span>
        <button onClick={() => setToggle((f) => !f)}>Toggle</button>
      </div>
      <img src={imageFilename} alt="a big file" />
    </div>
  );
}

You can try it here:
https://codesandbox.io/s/floral-resonance-elouv7

Image 2 (mountain.jpg) is about 1.5MB, and the issue is more profound if the file is like 5MB or 6MB.

So what is a good way to handle this? If possible, I'd hope not to use complicated methods, such as using a spinner and use a two step method perhaps to first show a blank image placeholder, and then set the src to the new one... because it is such an overkill for the purpose of just showing an image.

If it is Vanilla JavaScript, I did think of one quick method, which is to replace the whole <img /> each time:

elImageContainer.innerHTML = `<img src="${filename}" />`;

You can try it at
https://codesandbox.io/s/objective-bhabha-imdipn

But I don't think it can be done in React, which is what the project is using, because I think the new virtual DOM is created and is diff'ed with the previous virtual DOM, and there is no way to tell whether it is a new <img /> element like in the Vanilla JavaScript case.

Is there a good solution to this?

Stefanie Gauss
  • 425
  • 3
  • 9
  • I guess I don't follow - why can't you compress your images to help alleviate the load times? – Jake Mar 09 '23 at 19:54
  • "The issue is, for our current system, the images / photos are very big, such as 5MB or 6MB each, and even though we may have a system to convert them into thumbnail and smaller images, such as 50kb or 150kb, it may still be a few months away and not in our control." – Stefanie Gauss Mar 09 '23 at 19:56
  • Barring that, I think you could probably have two `` tags, one for each picture, and rather than updating the `src` attribute when toggling, just set one to `display: none`. You'll have some longer load time when you first visit the page, but the toggle would be faster – Jake Mar 09 '23 at 19:56
  • 1
    That doesn't answer my question. Compressing images is very easy, anyone can do it in a few minutes. Do you not have access to the server to upload the new, compressed images? – Jake Mar 09 '23 at 19:57
  • Also, your codesandbox link doesn't work for me. It shows a blank white webpage, and there are errors in the console. – Jake Mar 09 '23 at 19:58
  • that's because the photos are taken by a different team and the current system has nothing that'd compress what they upload. And that whole backend system is beyond our control for now. I just need to fix it so that the front end can behave well even when the data isn't like we'd hope it to be – Stefanie Gauss Mar 09 '23 at 20:04
  • can you refresh this page and try again for the new codesandbox links?... maybe I need to click Share it and copy the link from there – Stefanie Gauss Mar 09 '23 at 20:08
  • Seems that this particular codesandbox only works in Chrome and errors in Firefox for some reason - it's my bad for not trying both first, sorry. – Jake Mar 09 '23 at 21:39

1 Answers1

1

Mirroring my comments above, I think there are (at least) 3 easy ways to accomplish this

OPTION 1

Work with your backend team to get some new, smaller images. It's very reasonable to expect images for websites to be compressed; certainly 5+ MB is just too large for most use cases. Large images increase response time (as you have already pointed out), and for mobile users, can also cost them money via their cell data plan. This is the heavily preferred solution and will make many things in your site easier and better. If this is absolutely not possible for some reason...

OPTION 2

As mentioned above, you can preload both images, but have one hidden. Then the toggle button just toggles which one is hidden. I forked your sandbox and made this one to demonstrate the idea

This is still easy and resolves the visual issues. However, it forces you to load both images on the initial load of the page, which will slow down your load. This will be especially bad for customers that don't ever press the "toggle" button - they are paying the data and time cost to load an image that they never even see.

OPTION 3

If you want to keep a structure/flow like the one you already have, you could subscribe to the <img> onload event to only update the caption/description after the new image has been loaded. This StackOverflow has some discussion on this.

Jake
  • 862
  • 6
  • 10
  • for option 1: in a corporation with 20,000 employees, you cannot always tell the other team to do this and do this now. And I added in the question: even so, what if they want the preview modal to show the 5MB image? For option 2, it is not just "2" images... it is more like 10,000 images and there can be a few hundred more every few days. For option 3, I was thinking of this route too... there probably is not an `onloadstart` event so I have to rely on the user clicking on the button to show a spinning wheel, and then use the image's `onload` event to make the spinner disappear – Stefanie Gauss Mar 10 '23 at 00:06
  • option 3 would be similar to the AJAX pattern, but I never thought I would use that for an image loading – Stefanie Gauss Mar 10 '23 at 00:14
  • So just to sum everything up: 1. You have a database containing thousands of large images 2. From a webpage, you need to be able to request & load any one of those images, and have no way of predicting which one you are going to need, so you cannot pre-load any of them 3. You don't want to implement a spinner or other type of temporary UI state to inform the user that a load is happening, so you need to load the images almost instantly, even over a slow connection like 3G If this is all accurate, then I think that this problem is unsolvable. Something will need to change. – Jake Mar 10 '23 at 01:37
  • I never said I am not willing to add a spinner. And such as the Vanilla JS case, that's no spinner and it is a possible solution, right? By the way, I found that the Option 3 above also has an issue: if the user clicks "Next" once and then twice, now you have two `load` (finish) handler to make the spinner disappear, so you may have to disable the Next button, or if you want to allow the Next button, then you need to have "Is the load finish event related to my current photo. Hide spinner if yes"... a bit messy too – Stefanie Gauss Mar 10 '23 at 15:43