-2

I have a page that load a random image as background image. Something like this:

<!-- some other codes -->
<style>
.random-background {
    height: 100px;
    width: 100px;
    background-image: url("https://server-domain.com/random.php");
}
</style>
<div class="random-background"></div>
<!-- some other codes -->

https://server-domain.com/random.php will return a random image every time it is called, so using AJAX to call the link is not an option. I am writing a Chrome extension, so I have no control on the server behaviour. Is there a way, using JavaScript, I can get the exact image used as background as the user see on screen?


It may seems to be a duplicated with this question. The difference is in this question, the target element is a div instead of img, so in the suggested solution to that question,

context.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);

won't work. This will be the error message

Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)'. at :6:9

cytsunny
  • 4,838
  • 15
  • 62
  • 129
  • do you need it to be random for the first load, and then cached from there? or do you want a specific image every time that isn't a separate ajax call? – errorline1 Aug 23 '22 at 17:29
  • You want to get the image bytes rather than the URL of the background image, right? The URL is easy: [How to get background image URL of an element using JavaScript?](https://stackoverflow.com/q/14013131/215552) but I'm getting that you want the specific random image that was sent. – Heretic Monkey Aug 23 '22 at 17:32
  • 2
    This is likely a duplicate of [Javascript: how to get image as bytes from a page (without redownloading)](https://stackoverflow.com/q/62511789/215552) but it's hard to tell from the description alone. – Heretic Monkey Aug 23 '22 at 17:36
  • @Heretic Monkey Yes, your understanding is correct. I want the image bytes. – cytsunny Aug 23 '22 at 17:36
  • Does this answer your question? [Javascript: how to get image as bytes from a page (without redownloading)](https://stackoverflow.com/questions/62511789/javascript-how-to-get-image-as-bytes-from-a-page-without-redownloading) – Heretic Monkey Aug 23 '22 at 17:42
  • @HereticMonkey While that question may be useful in solving this question, I have edited the question to show the difference. – cytsunny Aug 23 '22 at 17:45
  • This will depend on the CORS policy for the server/script you are getting the random image from, but couldn't you just pull the image in via JavaScript first, convert it to a **dataURL** and then add it as the `background-image` property (as this will accept a **dataURL**)? – EssXTee Aug 23 '22 at 18:36
  • @EssXTee No, I cannot. As I am writing a Chrome extension, I don't have control on how the original page loads the image. – cytsunny Aug 23 '22 at 19:17

2 Answers2

0

I'm posting this as a potentially viable solution, but ultimately this depends on the CORS policy of the server generating the random images.

This somewhat uses the concept from Javascript: how to get image as bytes from a page (without redownloading), however to answer the differences outlined by OP, this works a little different.

The idea is that the image is first downloaded using JavaScript and applied to an Image() object created by JavaScript. When the image is done loading, it is then loaded into a canvas object on which we can apply toDataURL(). Finally this new image data can be applied to the background-image property of the <div> element and also used anywhere else needed (it would just need to be saved as global variable).

The server CORS policy has not been disclosed by OP nor has what OP needs to do with the image data, so that is not being addressed in this answer.

const _Base64Image = url => {
  const img = new Image();
  img.setAttribute('crossOrigin', 'anonymous');
  img.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    const dataURL = canvas.toDataURL("image/png");
    _SetBG(document.querySelector("#imgTest"), dataURL, img.width, img.height);
  }
  img.src = url;
}
const _SetBG = (el, data, w, h) => {
  el.style.width = `${w}px`;
  el.style.height = `${h}px`;
  el.style.backgroundImage = `url("${data}")`;
}

_Base64Image('https://nathanchapman.gallerycdn.vsassets.io/extensions/nathanchapman/javascriptsnippets/0.2.0/1510271465098/Microsoft.VisualStudio.Services.Icons.Default');
.random-background {
    height: 100px;
    width: 100px;
}
<div id="imgTest" class="random-background"></div>
EssXTee
  • 1,783
  • 1
  • 13
  • 18
  • I believe this may work for some cases, but as I am writing a Chrome extension, the image has already been loaded by the originall CSS, so I am unable to have javascript to load the image to `Image()` object at the first place. – cytsunny Aug 23 '22 at 19:16
  • @cytsunny As far as I can tell (based on the [CSS Spec](https://drafts.csswg.org/css-backgrounds/#background-image)), there isn't a way to get the `background-image` property as a base64 string without reloading the image. Even if you are writing an extension, my code would pull a random image, get the data, then replace the background image on the page with the one in my script (which can be saved as a variable). Without more details on what your extension is doing, I can't offer anything else. – EssXTee Aug 23 '22 at 19:26
0

Why you want to do this is still a mystery to me, and the following is surely a bad idea in most cases. It will cause performance issues. But if you really need it, here goes...

You can achieve this by intercepting each image request so that you can always store the response in JS before the browser can start using it. This could get very costly, especially if you need to perform this on every request.

All you have to do is make a web worker listen to the fetch event.

Something like this:

const images = {};

function doAnythingWith(request, response) {
    // response.url would not work as it changes on redirects.
    // request.url should have the URL from the CSS.
    images[request.url] = response.body;
}

self.addEventListener("fetch", (event) => {
  if (event.request.method !== "GET") return;
  
  // Optionally include some exlusion logic here.
  // Maybe by domain or file extension.
  
  // Prevent the default, and handle the request ourselves.
  event.respondWith(
    (async () => {
      
      const response = await fetch(event.request);

      doAnythingWith(request, response);
      
      return response;
    })()
  );
});

self.addEventListener("message", (event) => {
  if (event.type !== "get-loaded-image") return;
  
  return images[event.payload.url];
});

Then you should be able to get the image for any given URL, and it will be the one that the user got.

myWorker.postMessage({type: "get-loaded-image", payload: someUrl});

You can store this with the URL as a key. You can then send messages to this worker to retrieve the original content for a URL.

Or perhaps you don't need to send a message at all and execute all your logic directly in the worker.

inwerpsel
  • 2,677
  • 1
  • 14
  • 21