0

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>
Nigel
  • 585
  • 1
  • 5
  • 20
  • What's inside your html markup? XMLSerializer is synchronous, that's not the issue. IIRC some UA may fire the load event before the html content inside the foreignObject is fully loaded though, but that's only of there is some possibly async resources being loaded. – Kaiido Oct 14 '22 at 14:57
  • @Kaiido I've appended sample HTML to the end of my question. – Nigel Oct 14 '22 at 20:59
  • (Sorry for the long time without answer) I can't reproduce with your given markup: https://jsfiddle.net/mu19t36s/ One thing that could happen is that your canvas may hit the [canvas max size](https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element)? Otherwise, I don't see, sorry... If you can edit the fiddle in order to make it reproduce the issue for you, that'd be great. – Kaiido Oct 17 '22 at 05:38
  • 1
    Thank you so much for taking the trouble. I can't reproduce the problem using your jsfiddle either! I think I must have been wrong in assuming that the problem related to this HTMLCanvas class. I am beginning to suspect that it is an issue with A-Frame. I will see whether I can find a solution from A-frame experts and report back. – Nigel Oct 18 '22 at 08:37

0 Answers0