-1

I have a page with several book covers and a button to download them all.

Book covers are in String format and are a Google Books URL. I'm populating like this:

<section ref="capture" id="my-node">
  <figure v-for="book in books" :key="book.id">
    <img :src="book.thumbnail" :alt="`Book ${book.title}`" />
  </figure>
  <button type="button" @click="download">html2canvas</button>
</section>

To download the book covers I'm calling the following function:

download() {
  html2canvas(this.$refs["capture"], { allowTaint: true })
    .then((canvas) => {
      let link = document.createElement("a");
      link.download = "Livrero.png";
      link.href = canvas.toDataURL("image/png");
      link.click();
    })
},

Books is an object that contains a thumbnail property with values like this: https://books.google.com/books/content?id=_i6bDeoCQzsC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api

What makes me more confused is that if I were using values similar to this: https://images-na.ssl-images-amazon.com/images/I/41xShlnTZTL._SX218_BO1,204,203,200_QL40_FMwebp_.jpg everything would be working, but unfortunately the project does not allow this at the moment.


Previously I was using the property useCORS: true in html2canvas however it was returning a CORS policy error. So, when I removed it, I stopped having this error and started getting the following error:

DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement':
Tainted canvases may not be exported.

It was suggested that I use http://html2canvas.hertzen.com/proxy/ to succeed in the mission, but because I'm using Vue.js I don't know how to apply this.

ARNON
  • 1,097
  • 1
  • 15
  • 33
  • 1
    Well, I assume you're using a service that (stupidly) confirms that the URL is an image based on the file extension, in the which case if they don't have great input validation you can just add `&.jpg` or `&.png` to the end of your image URL. – code Jan 28 '22 at 01:44
  • @Arnon now I'm wondering why you're using a library that doesn't specialize in the task. Why don't you just follow [this answer](https://stackoverflow.com/questions/6011378/how-to-add-image-to-canvas)? – code Jan 28 '22 at 01:58
  • 1
    It works on Amazon because you are referencing an absolute path to a file stored on their server, thus suffixed `.jpg` (that's the way AWS works and it's their choice by design). Google, on the other hand, utilizes query strings to dynamically load an image. Not crazily conventional, but whatever Google does become convention, right? – code Jan 28 '22 at 02:09
  • 1
    @gre_gor how do you know that's the way the OP used html2canvas? If it was though, that would have been sort of a waste... (could have just loaded the image onto canvas) – code Jan 28 '22 at 02:10
  • @Arnon then can you add an example of the error you received along with your code? – code Jan 28 '22 at 02:17
  • Actually the code provided by @gre_gor helped. I was using `useCORS: true` and it was causing some errors. Now that I've omitted it, I'm getting another error: `DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.`. However, I will have to completely change my question because it completely changed course. – ARNON Jan 28 '22 at 02:30
  • http://html2canvas.hertzen.com/proxy/ You will need a CORS proxy. – gre_gor Jan 28 '22 at 02:37
  • Question is edited. I already tried to use html2canvas-proxy but without success. – ARNON Jan 28 '22 at 02:48

1 Answers1

0

This is a problem with browser's content policy. There is no simple solution.

The browser won't let you use content (images) from different origins which don't explicitly allow it.

With useCORS: true, it's expected that the server (with the remote resource) responds with the appropriate access-control-allow-origin headers. Your Amazon link does, that's why it works.

With allowTaint: true, it accepts the image, but then refuses to let you do anything with the resulting canvas image as it's now considered tainted.

Since you can't make 3rd party server send appropriate CORS headers, you will have to run your own CORS proxy.

To run html2canvas-proxy mentioned in the official documentation you need to install it

npm install html2canvas-proxy --save

make a start JavaScript file

var proxy = require('html2canvas-proxy');
var express = require('express');
const port = 8000;

var app = express();
app.use('/', proxy());
app.listen(port, () => {
  console.log(`html2canvas-proxy listening on port ${port}`)
})

and then use the proxy option in html2canvas

html2canvas(this.$refs["capture"], {
    proxy: "http://localhost:8000",
})
.then((canvas) => {
  // ...
})

But for production, this simple proxy isn't good either as it allows anyone to use is as a CORS proxy and you might not want a separate server, if you aren't not already running an Express Node.js server.

gre_gor
  • 6,669
  • 9
  • 47
  • 52
  • I don't see how Vue is relevant here. Vue is a frontend framework. It shouldn't matter what you run on the backend. – gre_gor Jan 28 '22 at 04:00
  • I was not running a backend before, now I am and this is working. I wonder if there isn't a solution without me having to run the backend. This will become annoying in production. – ARNON Jan 28 '22 at 04:07