Flickering problem when canvas is redrawn

Reason

When the canvas is emptied or redrawn, the previously drawn content will disappear instantly and then be redrawn, resulting in a visual flicker.

Solution

Create two canvases, one for caching data and the other for display. First, before clearing and redrawing, cache the new data to be drawn on the canvas for caching, and finally draw the cached canvas on the canvas for display. This method is also called double buffering method.

Case

<!-- double buffering technology to solve the problem of canvas redrawing flickering -->
<template>
    <div ref="container" class="container">
        <!-- <div class="mask">
            <span style="mix-blend-mode: multiply;">KOOND66</span>
        </div> -->
        <canvas id="myCanvas" :width="width" :height="height"></canvas>
        <canvas id="myCanvas1" :width="width" :height="height" style="opacity: 0;"></canvas>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                animationFrameId: null,
                isAdmation: false,
                flag: true,
                width: null,
                height: null,
                list: [],
                timer: null
            }
        },
        async mounted() {
            this.width = this.$refs.container.clientWidth
            this.height = this.$refs.container.clientHeight
            await this. computedCircle()
            window.addEventListener('resize', this.handleResize);
        },
        beforeDestroy() {
            window.cancelAnimationFrame(this.animationFrameId);
        },
        methods: {
            startAdmation() {
                this.requestAdmationStart()
            },
            requestAdmationStart() {
                this. move()
                window.cancelAnimationFrame(this.animationFrameId);
                this.animationFrameId = window.requestAnimationFrame(this.requestAdmationStart)
            },
            handleResize() {
                this.width = this.$refs.container.clientWidth
                this.height = this.$refs.container.clientHeight
                // this.initBall(true)
                let canvas = document. getElementById('myCanvas')
                let content = canvas. getContext('2d')
                let tempCanvas = document. getElementById('myCanvas1');
                let tempCtx = tempCanvas. getContext('2d');
                tempCtx. clearRect(0, 0, tempCtx. canvas. width, tempCtx. canvas. height)
                this.list.forEach(item => {
                    tempCtx.beginPath()
                    tempCtx.arc(item.x, item.y, item.r, 0, Math.PI * 2, false)
                    tempCtx.fillStyle = item.color
                    tempCtx. fill()
                })
                const imageData = tempCtx.getImageData(0, 0, tempCtx.canvas.width, tempCtx.canvas.height);
                //content.clearRect(0, 0, content.canvas.width, content.canvas.height)
                content.putImageData(imageData, 0, 0); // render to the displayed canvas
                // this. startAdmation()
            },
            computedCircle() {
                let canvas = document. getElementById('myCanvas')
                let content = canvas. getContext('2d')
                for(let i = 0; i < 10; i ++ ) {
                    let newR = 50 * Math. random() + 10
                    this.list[i] = {
                        r: newR,
                        x: content.canvas.width * Math.random() <= newR + 200 ? content.canvas.width * Math.random() + newR + 200: content.canvas.width * Math.random() > content.canvas .width - newR ? content.canvas.width - newR : content.canvas.width * Math.random(),
                        y: content.canvas.height * Math.random() <= newR + 200 ? content.canvas.height * Math.random() + newR + 200: content.canvas.height * Math.random() > content.canvas .height - newR ? content.canvas.height - newR : content.canvas.height * Math.random(),
                        vx: 4 * Math. random()-2,
                        vy: 4 * Math. random()-2,
                        color: 'rgba(' +
                            (Math.random() * 255).toFixed(0) + ',' +
                            (Math.random() * 255).toFixed(0) + ',' +
                            (Math.random() * 255).toFixed(0) + ',' +
                            (0.5 + 0.5*Math. random()). toFixed(1) + ')'
                    }
                }
                this.initBall(true)
            },
            initBall(flag) {
                let canvas = document. getElementById('myCanvas')
                let content = canvas. getContext('2d')
                content. clearRect(0, 0, content. canvas. width, content. canvas. height)
                this.list.forEach(item => {
                    content.beginPath()
                    content.arc(item.x, item.y, item.r, 0, Math.PI * 2, false)
                    content.fillStyle = item.color
                    content. fill()
                })
                if(flag) {
                    this. startAdmation()
                }
            },
            move() {
                let canvas = document. getElementById('myCanvas')
                let content = canvas. getContext('2d')
                this.list.forEach(item => {
                    if(item.x + item.vx > content.canvas.width - item.r || item.x + item.vx < 0 + item.r) {
                        item.vx = -item.vx
                    }
                    if(item.y + item.vy > content.canvas.height - item.r || item.y + item.vy < 0 + item.r) {
                        item.vy = -item.vy
                    }
                    item.x += item.vx
                    item.y += item.vy
                })
                let tempCanvas = document. getElementById('myCanvas1');
                let tempCtx = tempCanvas. getContext('2d');
                tempCtx. clearRect(0, 0, tempCtx. canvas. width, tempCtx. canvas. height)
                this.list.forEach(item => {
                    tempCtx.beginPath()
                    tempCtx.arc(item.x, item.y, item.r, 0, Math.PI * 2, false)
                    tempCtx.fillStyle = item.color
                    tempCtx. fill()
                })
                const imageData = tempCtx.getImageData(0, 0, tempCtx.canvas.width, tempCtx.canvas.height); // cache the latest data first
                content. clearRect(0, 0, content. canvas. width, content. canvas. height)
                content.putImageData(imageData, 0, 0);
            }
        }
    }
</script>

<style lang="less" scoped>
    .container {
        width: 80%;
        height: 80%;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
    canvas {
        width: 100%;
        height: 100%;
        border: 1px solid #eee;
    }
    .mask {
        width: 100%;
        height: 100%;
        position: absolute;
        background-color: black;
        left: 0;
        top: 0;
        font-size: 100px;
        font-weight: 700;
        // color: aliceblue;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    #myCanvas1 {
        position: absolute;
        left: -9999999999999999px;
        top: -99999999999999px;
    }
</style>