I am generating a canvas from styled HTML code, using serializer.serializeToString
to create an SVG string, encoding the string and setting an IMG src
to it, and then using drawImage
to a canvas context. This works, in the sense that sometimes, especially with very simple HTML, the desired image is drawn on the canvas. But often, the image doesn't end up on the canvas (the canvas remains blank/transparent). I suspect that this is a timing issue. I am using
this.img.addEventListener('load', () => { this.render() })
to wait until the SVG is loaded into the image before rendering the image to the canvas, so that should not be the problem. Do I have to wait for serializeToString
and if so, how?
A somewhat simplified version of the code is below.
[If you want to know why I want to do this, the context is that I am developing a VR app using a-frame, and I want to dynamically generate a display panel (a canvas) from HTML code to be placed in VR.]
class HTMLCanvas {
constructor(html) {
if (!html) throw 'Container Element is Required'
// Create the canvas to be drawn to
this.canvas = document.createElement('canvas')
this.ctx = this.canvas.getContext('2d')
// Set some basic styles for the embedded HTML
this.html = html
this.html.style.position = 'absolute'
this.html.style.top = '0'
this.html.style.left = '0'
this.html.style.overflow = 'hidden'
this.html.style.fontFamily = 'sans-serif'
// Image used to draw SVG to the canvas element
this.img = new Image()
this.img.addEventListener('load', () => {
this.render()
})
this.serializer = new XMLSerializer()
this.svgToImg()
}
// Set the src to be rendered to the Image
svgToImg() {
// Make sure the element is visible before processing
this.html.style.display = 'block'
this.width = this.html.offsetWidth
this.height = this.html.offsetHeight
this.canvas.width = this.width
this.canvas.height = this.height
let docString = this.serializer.serializeToString(this.html)
docString =
'<svg width="' +
this.width +
'" height="' +
this.height +
`" xmlns="http://www.w3.org/2000/svg"><foreignObject x="0" y="0" width="` +
(this.width + 20) +
'" height="' +
(this.height + 20) +
'">' +
docString +
'</foreignObject></svg>'
this.img.src = 'data:image/svg+xml;utf8,' + encodeURIComponent(docString)
// Hide the html after processing
this.html.style.display = 'none'
}
// Renders the image containing the SVG to the Canvas
render() {
this.canvas.width = this.width
this.canvas.height = this.height
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.ctx.drawImage(this.img, 0, 0)
}
}
Here's the HTML I have been using for testing. It is all styled inline because <foreignObject>
doesn't like CSS <style>
(not quite true, but it gets a lot more complicated):
<div style="background: rgba(255, 255, 255, 0.2); border-radius: 16px; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); border: 1px solid rgba(255, 255, 255, 0.3); padding: 20px; width: 300px; font-family: Arial, Helvetica, sans-serif;">
<div style="text-align: center; font-weight: bold">Key</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr);">
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: #9adbb4">
</div>
<div>
General Factors
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(255, 255, 0)">
</div>
<div>
Private Transport
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: #dba542">
</div>
<div>
Outcomes
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(219, 110, 103)">
</div>
<div>
Active travel
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: #8371b7">
</div>
<div>
COVID-related
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(100, 114, 143)">
</div>
<div>
Rail
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(255, 0, 0)">
</div>
<div>
Bus
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(255, 255, 153)">
</div>
<div>
Laws and regulations
</div>
</div>
<div style="justify-self: center; margin: 10px;">
<div style="width: 40px; height: 40px; border-radius: 50%; margin: auto; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; background-color: rgb(0, 255, 0)">
</div>
<div>
Neighbourhood
</div>
</div>
</div>
<div style=" display: grid; grid-template-columns: repeat(3, 1fr);">
<div style=" width: 40px; height: 40px; justify-self: center; margin: 10px; text-align: center;">
<div style="color: rgb(0, 0, 0)">
<svg style= "filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 0.4));" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-arrow-right" viewbox="0 0 16 16">
<path stroke="currentColor" stroke-width="1.2" fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
</svg>
</div>
<div>
COMPLEX
</div>
</div>
<div style=" width: 40px; height: 40px; justify-self: center; margin: 10px; text-align: center;">
<div style="color: #00cc00">
<svg style= "filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 0.4));" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-arrow-right" viewbox="0 0 16 16">
<path stroke="currentColor" stroke-width="1.2" fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
</svg>
</div>
<div>
+VE
</div>
</div>
<div style=" width: 40px; height: 40px; justify-self: center; margin: 10px; text-align: center;">
<div style="color: #ff0000">
<svg style= "filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 0.4));" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-arrow-right" viewbox="0 0 16 16">
<path stroke="currentColor" stroke-width="1.2" fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
</svg>
</div>
<div>
-VE
</div>
</div>
<div style=" width: 40px; height: 40px; justify-self: center; margin: 10px; text-align: center;">
<div style="color: #008000">
<svg style= "filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 0.4));" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-arrow-right" viewbox="0 0 16 16">
<path stroke="currentColor" stroke-width="1.2" fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
</svg>
</div>
<div>
SUGGESTED (+VE)
</div>
</div>
</div>
</div>