diff --git a/src/components/CancelableImg.js b/src/components/CancelableImg.js new file mode 100644 index 00000000..ed9ccce3 --- /dev/null +++ b/src/components/CancelableImg.js @@ -0,0 +1,32 @@ +import React, { useState, useEffect } from "react"; + +// When the user is scrolling quickly, we can render a ton of images. So many +// that we saturate the number of concurrent network requests that the browser +// will allow. +// +// With a naive implement, this leads to images that are in the viewport being +// blocked because images that we've scrolled past are still in the queue to +// download. By removing the `src` attribute, we signal to the browser that the +// request can be canceled. +// +// Another approach I tried was issuing a `fetch` request for the image with an +// `AbortController` to allow canceling on unmount, and only adding the `src` to +// the image _after_ the `fetch` completed. The ideas was that the `` would +// end up loading the image from the cache which was warmed by `fetch`. However, +// on Safari, it looks like it ends up making two requests. Perhaps it thinks +// the fetch/img requests look too different due to different headers or +// something. +export default function CancelableImg(props) { + const [ref, setRef] = useState(null); + useEffect(() => { + return () => { + if (ref != null) { + // This will cause Chrome and Firefox to cancel the request. + // Safari does not seem to get the message. + ref.removeAttribute("src"); + } + }; + }, [ref]); + + return ; +} diff --git a/src/components/Skin.js b/src/components/Skin.js index 99abec76..43e7e781 100644 --- a/src/components/Skin.js +++ b/src/components/Skin.js @@ -1,6 +1,7 @@ import React, { useState, useCallback } from "react"; import * as Utils from "../utils"; import { SCREENSHOT_HEIGHT } from "../constants"; +import CancelableImg from "./CancelableImg"; function Skin({ style, @@ -85,7 +86,7 @@ function Skin({ onClick={clickHandler} href={permalink} > -