You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

heatmap.js动画使用报错:Canvas2dRenderer._colorize源高度为0的解决方法

修复heatmap.js动画报错:Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source height is 0

我参考heatmap.js的动画示例实现功能时,遇到了如下报错:

heatmap.js:490 Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source height is 0. at Canvas2dRenderer._colorize

我的实现代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>heatmap.js</title>
        <style>
            body,
            html,
            h2 {
                margin: 0;
                padding: 0;
                height: 100%;
            }
            body {
                font-family: sans-serif;
            }
            body * {
                font-weight: 200;
            }

            #heatmapContainerWrapper {
                width: 100%;
                height: 100%;
                position: absolute;
                background: rgba(0, 0, 0, 0.1);
            }
            #heatmapContainer {
                width: 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <div class="timeline-wrapper"></div>
        <div class="heatmap"></div>
        <div id="heatmapContainerWrapper">
            <div id="heatmapContainer"></div>
        </div>

        <script src="/build/heatmap.js"></script>
        <script>
            window.onload = function () {
                // creating a class to wrap the heatmap cycling logic
                function AnimationPlayer(options) {
                    this.heatmap = options.heatmap
                    this.data = options.data
                    this.interval = null
                    this.animationSpeed = options.animationSpeed || 300
                    this.wrapperEl = options.wrapperEl
                    this.isPlaying = false
                    this.init()
                }
                // define the prototype functions
                AnimationPlayer.prototype = {
                    init: function () {
                        var dataLen = this.data.length
                        this.wrapperEl.innerHTML = ''
                        var playButton = (this.playButton =
                            document.createElement('button'))
                        playButton.onclick = function () {
                            if (this.isPlaying) {
                                this.stop()
                            } else {
                                this.play()
                            }
                            this.isPlaying = !this.isPlaying
                        }.bind(this)
                        playButton.innerText = 'play'

                        this.wrapperEl.appendChild(playButton)

                        var events = document.createElement('div')
                        events.className = 'heatmap-timeline'
                        events.innerHTML = ''

                        for (var i = 0; i < dataLen; i++) {
                            var xOffset = (100 / (dataLen - 1)) * i

                            var ev = document.createElement('div')
                            ev.className = 'time-point'
                            ev.style.left = xOffset + '%'

                            ev.onclick = function (i) {
                                return function () {
                                    this.isPlaying = false
                                    this.stop()
                                    this.setFrame(i)
                                }.bind(this)
                            }.bind(this)(i)

                            events.appendChild(ev)
                        }
                        this.wrapperEl.appendChild(events)
                        this.setFrame(0)
                    },
                    play: function () {
                        var dataLen = this.data.length
                        this.playButton.innerText = 'pause'
                        this.interval = setInterval(
                            function () {
                                this.setFrame(++this.currentFrame % dataLen)
                            }.bind(this),
                            this.animationSpeed
                        )
                    },
                    stop: function () {
                        clearInterval(this.interval)
                        this.playButton.innerText = 'play'
                    },
                    setFrame: function (frame) {
                        this.currentFrame = frame
                        var snapshot = this.data[frame]
                        this.heatmap.setData(snapshot)
                        var timePoints = $('.heatmap-timeline .time-point')
                        for (var i = 0; i < timePoints.length; i++) {
                            timePoints[i].classList.remove('active')
                        }
                        timePoints[frame].classList.add('active')
                    },
                    setAnimationData: function (data) {
                        this.isPlaying = false
                        this.stop()
                        this.data = data
                        this.init()
                    },
                    setAnimationSpeed: function (speed) {
                        this.isPlaying = false
                        this.stop()
                        this.animationSpeed = speed
                    },
                }

                var heatmapInstance = h337.create({
                    container: document.querySelector('.heatmap'),
                })

                // animationData contains an array of heatmap data objects
                var animationData = []

                console.log(document.body.clientHeight)
                function generateRandomData(len) {
                    var max = 100
                    var min = 1
                    var maxX = document.body.clientWidth
                    var maxY = document.body.clientHeight
                    var data = []
                    while (len--) {
                        data.push({
                            x: (Math.random() * maxX) >> 0,
                            y: (Math.random() * maxY) >> 0,
                            radius: (Math.random() * 50 + min) >> 0,
                        })
                    }
                    return {
                        max: max,
                        min: min,
                        data: data,
                    }
                }
                // generate some heatmap data objects
                for (var i = 0; i < 20; i++) {
                    animationData.push(generateRandomData(300))
                }

                var player = new AnimationPlayer({
                    heatmap: heatmapInstance,
                    wrapperEl: document.querySelector('.timeline-wrapper'),
                    data: animationData,
                    animationSpeed: 100,
                })
            }
        </script>
    </body>
</html>

问题根源

报错的核心原因是heatmap容器.heatmap没有设置宽高,导致heatmap.js初始化时无法获取有效的画布尺寸,进而调用getImageData时出现高度为0的错误。

修复步骤

  1. 为heatmap容器设置明确宽高
    修改CSS,给.heatmap添加宽高属性,比如占满整个视口:

    .heatmap {
        width: 100vw;
        height: 100vh;
        position: relative;
    }
    

    或者直接复用已有的#heatmapContainerWrapper容器,将heatmap实例挂载到#heatmapContainer上,删除冗余的.heatmap元素。

  2. 修复jQuery依赖缺失问题
    代码中setFrame方法使用了$('.heatmap-timeline .time-point'),但页面未引入jQuery,替换为原生DOM API即可:

    var timePoints = document.querySelectorAll('.heatmap-timeline .time-point');
    

其他实现heatmap.js动画的思路

  • 用requestAnimationFrame替代setInterval
    与浏览器刷新帧率同步,动画更流畅,避免掉帧:
    play: function () {
        this.playButton.innerText = 'pause';
        this.isPlaying = true;
        const animate = () => {
            if (!this.isPlaying) return;
            this.setFrame(++this.currentFrame % this.data.length);
            requestAnimationFrame(animate);
        };
        animate();
    },
    stop: function () {
        this.isPlaying = false;
        this.playButton.innerText = 'play';
    }
    
  • 增量更新数据
    如果帧之间数据差异小,可使用heatmapInstance.addData()增量添加数据,配合setData重置,减少渲染开销。

内容的提问来源于stack exchange,提问作者MOLLY

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 17:01:06