如何通过IntersectionObserver控制AMCharts地球动画启停
问题根源
你之前的代码不生效,核心是三个问题:
- 自定义的
cancelAnimationFrame拿不到AMCharts内部维护的渲染帧ID,相当于对着空气喊停,完全碰不到实际运行的渲染逻辑 - 配置项
am4core.options.onlyShowOnViewport = true仅控制图表首次懒加载时机,图表加载完成后滚出视口,不会自动停止已经启动的渲染循环 - 单独停止经度旋转的补间动画没用,AMCharts内部的全局渲染循环还在持续运行,依然会占用大量系统资源
实现方案
AMCharts v4 原生提供了渲染启停、动画暂停的内置方法,不需要自己手动拦截帧循环:
- 用图表实例自带的
chart.sleep()方法终止内部所有渲染循环,完全释放帧资源占用;回到视口时调用chart.wake()恢复渲染 - 旋转动画用内置的
pause()/resume()方法控制暂停和续播,保留动画进度,不会出现回到视口动画重置跳变的问题 - 加状态标记避免滚动过程中重复触发启停逻辑,给IntersectionObserver加阈值减少边缘抖动
修正后完整代码
JS部分(替换原globe.js内容即可)
/** * --------------------------------------- * This demo was created using amCharts 4. * --------------------------------------- */ // Themes begin am4core.useTheme(am4themes_animated); // Themes end am4core.options.queue = true; am4core.options.onlyShowOnViewport = true; var chart = am4core.create("chartdiv", am4maps.MapChart); // Set map definition chart.geodata = am4geodata_worldLow; // Set projection chart.projection = new am4maps.projections.Orthographic(); chart.panBehavior = "none"; chart.deltaLatitude = -20; chart.padding(20,20,20,20); // Create map polygon series var polygonSeries = chart.series.push(new am4maps.MapPolygonSeries()); polygonSeries.useGeodata = true; // Configure series var polygonTemplate = polygonSeries.mapPolygons.template; polygonTemplate.fill = am4core.color("#d5ebfe"); polygonTemplate.stroke = am4core.color("#fff"); polygonTemplate.strokeWidth = 0.0; var graticuleSeries = chart.series.push(new am4maps.GraticuleSeries()); graticuleSeries.mapLines.template.line.stroke = am4core.color("#00000"); graticuleSeries.mapLines.template.line.strokeOpacity = 0.00; graticuleSeries.fitExtent = false; chart.maxZoomLevel = 1; chart.backgroundSeries.mapPolygons.template.polygon.fillOpacity = 0.0; chart.backgroundSeries.mapPolygons.template.polygon.fill = am4core.color("#ffffff"); // 存储旋转动画实例、运行状态标记 let rotationAnimation; let isChartRunning = false; // 初始化旋转动画 setTimeout(function(){ if(!rotationAnimation){ rotationAnimation = chart.animate({property:"deltaLongitude", to:100000}, 20000000); // 初始化完成先暂停,等进入视口再启动 rotationAnimation.pause(); } }, 3000) chart.seriesContainer.events.on("down", function(){ if(rotationAnimation) rotationAnimation.stop(); }) // 启动渲染 function startChart() { if(isChartRunning) return; chart.wake(); if(rotationAnimation) rotationAnimation.resume(); isChartRunning = true; console.log('render has been started'); } // 停止渲染 function stopChart() { if(!isChartRunning) return; if(rotationAnimation) rotationAnimation.pause(); chart.sleep(); isChartRunning = false; console.log('render has been halted'); } const statusElem = document.querySelector('.status'); const onScreen = new Set(); const intersectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { onScreen.add(entry.target); startChart(); } else { onScreen.delete(entry.target); stopChart(); } }); statusElem.textContent = onScreen.size ? `on screen: ${[...onScreen].map(e => e.id).join(', ')}` : 'none'; }, { // 露出10%时触发启动,避免边缘反复抖动 threshold: 0.1 }); document.querySelectorAll('#chartdiv').forEach(elem => intersectionObserver.observe(elem));
HTML部分(修复重复status元素的问题)
<body> <script src="https://www.amcharts.com/lib/4/core.js"></script> <script src="https://www.amcharts.com/lib/4/maps.js"></script> <script src="https://www.amcharts.com/lib/4/geodata/worldLow.js"></script> <script src="https://www.amcharts.com/lib/4/themes/animated.js"></script> <div class="status"></div> <header id="header"></header> <div class="section" id="section0"> <div class="intro"> <br><br><br><br><br><br><br><br><br><br><br><br> <div id="chartdiv"></div> <script src="js/globe.js"></script> <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> </div> </div> </body>
原CSS代码不需要修改,可以直接使用。
效果说明
- 图表滚出视口后,内部渲染循环会被完全终止,CPU/GPU占用直接降到接近0,不会再拖慢页面其他动画
- 旋转动画进度会被保留,滚回视口时从暂停位置继续旋转,不会出现角度跳变
- 加了重复执行拦截,滚动过程中不会反复触发启停逻辑,性能损耗可以忽略
内容的提问来源于stack exchange,提问作者David Morton
相关产品推荐
相关产品推荐

