低JS依赖的环形进度与折线图实现:适配多端及PDF导出
优化方案:低JS依赖的环形进度图与折线图(适配PDF导出)
需求概述
- 用HTML/CSS/JS(Chart.js)实现环形进度图+折线图,尽可能减少JS依赖
- 确保在所有浏览器正常渲染,且导出PDF后在iOS、Acrobat等阅读器无显示故障
- 环形进度图以12点钟为顶部基准,需实现三种状态:
- 进度恰好到12点:蓝色渐变
- 进度未到12点:淡绿色渐变,进度未填满
- 进度超过12点:紫色渐变,进度末端深色渐变+圆角,端点样式随偏离12点距离变化
当前实现代码
HTML
<head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Age Progress and Graph</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script> </head> <body> <div class="progress-container"> <svg width="160" height="160"> <circle cx="80" cy="80" r="70" stroke="#eee" stroke-width="8" fill="none" ></circle> <circle cx="80" cy="80" r="70" stroke="url(#gradient)" stroke-width="8" fill="none" stroke-dasharray="440" stroke-dashoffset="{{DASH_OFFSET}}" stroke-linecap="round" ></circle> <defs> <linearGradient id="gradient" x1="1" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#a646d7" /> <stop offset="100%" stop-color="#e84fd1" /> </linearGradient> </defs> </svg> <div class="progress-text"> <h2 style="font-weight:300;">44.37</h2> <small>Older ⤴</small> </div> </div> <div class="chart-container"> <canvas id="ageChart"></canvas> </div> </body>
CSS
body { font-family: sans-serif; display: flex; align-items: center; justify-content: space-between; background: #fff; margin: 0; padding: 2rem; width: 700px; } .progress-container { position: relative; width: 160px; height: 160px; margin-bottom: 2rem; } .progress-container svg { transform: rotate(-90deg); } .progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .progress-text h2 { margin: 0; font-size: 2rem; } .chart-container { width: 440px; height: 150px; }
JavaScript
Chart.register(ChartDataLabels); const currentAge = 42; const biologicalAge = 44.37; const fullCircle = 440; const percent = Math.min(biologicalAge / currentAge, 1); const offset = fullCircle * (1 - percent); document.querySelector("circle[stroke-dashoffset='{{DASH_OFFSET}}']") .setAttribute("stroke-dashoffset", offset); const ctx = document.getElementById('ageChart').getContext('2d'); const dataPoints = [40.44, 45.54, 44.37]; const ageChart = new Chart(ctx, { type: 'line', data: { labels: ['Jan 2024', 'Apr 2024', 'Jul 2024'], datasets: [{ label: 'Biological Age', data: dataPoints, fill: false, tension: 0.4, borderColor: function(context) { const chart = context.chart; const {ctx, chartArea} = chart; if (!chartArea) return; const gradient = ctx.createLinearGradient(chartArea.left, 0, chartArea.right, 0); gradient.addColorStop(0, '#36d1dc'); gradient.addColorStop(1, '#a646d7'); return gradient; }, borderWidth: 3, pointRadius: 0 }] }, options: { responsive: true, maintainAspectRatio: false, layout: { padding: { left: 20, right: 20 } }, scales: { y: { suggestedMin: 35, suggestedMax: 50, ticks: { stepSize: 5 }, grid: { drawTicks: true, drawOnChartArea: true } }, x: { grid: { drawTicks: false, drawOnChartArea: false, drawBorder: false, color: 'transparent', lineWidth: 0 }, border: { display: false }, ticks: { padding: 8 }, offset: true } }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => `Age: ${ctx.raw}` } }, datalabels: { align: 'center', anchor: 'center', formatter: (value) => value.toFixed(2), backgroundColor: (context) => context.dataIndex === context.dataset.data.length - 1 ? '#000' : '#fff', borderRadius: 999, color: (context) => context.dataIndex === context.dataset.data.length - 1 ? '#fff' : '#000', font: { weight: 'bold' }, padding: 6, borderWidth: 1, borderColor: '#000' } } }, plugins: [ChartDataLabels] });
优化建议
1. 环形进度图:减少JS依赖,适配三种状态
核心思路:用CSS+少量JS切换渐变与状态,避免复杂JS计算
- 预定义三种渐变:在SVG的
<defs>中提前定义蓝色、淡绿色、紫色三种渐变<defs> <!-- 恰好到12点:蓝色渐变 --> <linearGradient id="gradient-blue" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#36d1dc" /> <stop offset="100%" stop-color="#29b6f6" /> </linearGradient> <!-- 未到12点:淡绿色渐变 --> <linearGradient id="gradient-green" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#81c784" /> <stop offset="100%" stop-color="#a5d6a7" /> </linearGradient> <!-- 超过12点:紫色渐变(末端深色) --> <linearGradient id="gradient-purple" x1="1" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#7b1fa2" /> <stop offset="70%" stop-color="#a646d7" /> <stop offset="100%" stop-color="#e84fd1" /> </linearGradient> </defs> - JS动态切换渐变与端点样式:根据
percent值判断状态,直接修改stroke属性和stroke-linecapconst progressCircle = document.querySelector("circle[stroke-dashoffset='{{DASH_OFFSET}}']"); progressCircle.setAttribute("stroke-dashoffset", offset); // 移除模板占位符,设置初始值 progressCircle.setAttribute("stroke-dashoffset", "440"); // 判断状态并切换样式 if (percent === 1) { // 恰好到12点 progressCircle.setAttribute("stroke", "url(#gradient-blue)"); progressCircle.setAttribute("stroke-linecap", "butt"); // 无圆角,刚好闭合 } else if (percent < 1) { // 未到12点 progressCircle.setAttribute("stroke", "url(#gradient-green)"); progressCircle.setAttribute("stroke-linecap", "round"); } else { // 超过12点(移除Math.min限制,根据需求保留) progressCircle.setAttribute("stroke", "url(#gradient-purple)"); // 端点圆角大小随偏离距离变化:percent越大,圆角越大 // 注:SVG原生stroke-linecap不支持动态半径,若需精细控制,可在进度末端添加圆形元素用JS定位 progressCircle.setAttribute("stroke-linecap", "round"); }
2. 折线图:降低JS复杂度,提升PDF兼容性
- 移除不必要的插件:若
chartjs-plugin-datalabels导致PDF渲染问题,改用静态HTML文本标注数据点,完全去掉插件依赖<!-- 在.chart-container中添加静态数据标签 --> <div class="chart-container"> <canvas id="ageChart"></canvas> <div class="data-labels"> <span style="left: 10%; top: 30%;">40.44</span> <span style="left: 50%; top: 10%;">45.54</span> <span style="left: 90%; top: 20%;" class="last-label">44.37</span> </div> </div>.chart-container { position: relative; width: 440px; height: 150px; } .data-labels span { position: absolute; padding: 4px 8px; border-radius: 999px; font-weight: bold; font-size: 12px; } .data-labels span:not(.last-label) { background: #fff; color: #000; border: 1px solid #000; } .last-label { background: #000; color: #fff; } - 禁用Chart.js动态特性:在options中关闭动画,确保导出时是静态渲染结果
options: { animation: false, // 关键:避免PDF捕获未完成的动画状态 // ...其他配置 }
3. 整体PDF兼容性优化
- 内联所有资源:把Chart.js和CSS代码内联到HTML中,避免CDN资源加载失败导致PDF渲染异常
- 固定尺寸:确保所有容器使用固定像素尺寸,避免响应式布局在PDF中变形
- 优先用浏览器导出:使用浏览器自带的"打印"功能选择"保存为PDF",兼容性优于第三方工具
内容的提问来源于stack exchange,提问作者Straxx
相关产品推荐
相关产品推荐

