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}
>
- 