我是设计师邱兴,一个学习前端的设计师,今天给大家制作一个用SVG实现的自定义电池电量进度,SVG相较于Echart来说制作简单,但是效果可以非常丰富。
一、目标
通过HTML、CSS和JavaScript创建一个交互式的人体成分可视化工具,实现以下功能:
- 使用SVG绘制人体轮廓并填充不同成分。
- 通过滑块动态调整各成分比例。
- 显示各成分的图例和提示信息。
二、所需工具与准备
-
工具:
- 一个文本编辑器(如Notepad++、VS Code等)。
- 浏览器(用于预览效果)。
-
基础准备:
- 确保你对HTML、CSS和JavaScript有一定的了解。
- 确保你对SVG的基本语法有一定了解。
三、代码分析与操作步骤
1. 创建HTML结构
创建一个HTML文件(如Lesson10.html)并设置基本的HTML结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人体成分可视化</title>
<style>
/* 样式部分 */
</style>
</head>
<body>
<h1>人体成分组成可视化</h1>
<div class="container">
<div id="body-visualization-container">
<svg id="body-visualization" width="250" height="500"></svg>
<div id="tooltip"></div>
</div>
<div class="controls">
<h2>调整成分比例</h2>
<div id="sliders"></div>
<ul id="legend"></ul>
</div>
</div>
<script>
// JavaScript部分
</script>
</body>
</html>
2. 添加CSS样式
在<style>标签中,添加以下CSS样式:
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
min-height: 100vh;
background-color: #f4f7f9;
margin: 0;
}
.container {
display: flex;
align-items: flex-start;
gap: 50px;
margin-top: 20px;
}
#body-visualization-container {
position: relative;
width: 250px;
height: 500px;
}
#tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.75);
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s, transform 0.2s;
transform: translate(-50%, -120%);
white-space: nowrap;
}
.controls {
width: 300px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.control-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-size: 14px;
}
.control-group input[type="range"] {
width: 100%;
cursor: pointer;
}
.control-group span {
float: right;
font-weight: bold;
color: #333;
}
#legend {
margin-top: 20px;
list-style: none;
padding: 0;
}
.legend-color {
width: 18px;
height: 18px;
margin-right: 10px;
border-radius: 4px;
border: 1px solid rgba(0,0,0,0.1);
}
body:设置页面字体、布局和背景颜色。.container:设置容器的布局和间距。#body-visualization-container:设置人体可视化容器的宽度和高度。#tooltip:设置提示框的样式。.controls:设置控制面板的样式。.control-group:设置滑块和标签的样式。#legend:设置图例的样式。
3. 编写JavaScript代码
在<script>标签中,添加以下JavaScript代码来实现人体成分可视化和交互功能:
const svg = document.getElementById('body-visualization');
const slidersContainer = document.getElementById('sliders');
const legendContainer = document.getElementById('legend');
const tooltip = document.getElementById('tooltip');
const svgContainer = document.getElementById('body-visualization-container');
const svgWidth = 250;
const svgHeight = 500;
// 默认的人体成分数据
let bodyData = {
'水': { percentage: 70, color: '#3498db' },
'蛋白质': { percentage: 15, color: '#e74c3c' },
'脂肪': { percentage: 10, color: '#f1c40f' },
'无机盐': { percentage: 5, color: '#95a5a6' }
};
// 人体轮廓SVG路径
const bodyPath = "M234.9,228c-7.3-1.5-14-18.9-16-26.6-1.5-7.7-5.8-27.6-18.9-44-4.8-6.3,0-10.6-8.7-34.8-7.3-19.8-5.3-37.2-19.8-42.6-12.1-4.4-26.1-2.9-31-21.3-1-3.4,0-4.8,1-8.7,2.9-5.8.5-9.7,2.4-8.7s9.2-12.6,2.4-12.6,3.4-8.2-5.3-17.9c-4.4-4.8-8.2-6.3-14.5-5.8-6.3,0-10.6,1-14.5,5.8-8.7,10.2-3.9,28.5-8.7,34.8-12.6,16.9-1,17.9-5.3,17.9-7.3,0,0,13.5,2.4,12.6,1.5-1,1,0,3.4,2.4,8.7,1.5,3.9,1.9,5.3,1,8.7-5.3,17.9-19.3,16.9-31,21.3-14.5,5.3-12.6,22.2-19.8,42.6-8.7,24.2-3.9,28.5-8.7,34.8-12.6,16.9-16.9,36.3-18.9,44-1.5,7.7-8.7,25.6-16,26.6-5.8,1-17.9,16.9-15,18.9,4.8,3.4,7.7-7.7,9.2-3.9,1,3.9-12.6,20.3-8.2,24.2,4.4,3.9,11.1-17.4,11.6-13.1,0,4.4-7.7,18.9-2.9,20.3,4.8,1,5.8-19.8,7.7-17.9,1.5,1.5-2.9,21.8,1.5,20.8s1.5-20.8,4.8-19.8c3.4,1,1,15.5,3.9,15.5s.5-11.1,2.4-14c1.5-3.4,3.9-17.4,4.8-22.7.5-4.8,0-10.2,8.7-23.2,9.7-13.1,21.8-31,24.7-40.6,1.5-5.3,0-9.7,3.9-15.5,3.4-4.4,5.8-9.7,7.3-12.6,0-1,1.5-1,2.4,0,3.9,4.8,12.6,22.2,1.5,69.6-13.5,59-5.8,88.5-5.3,98.7,0,8.2,3.4,19.3-1,40.6-1,5.3-4.4,13.1-5.3,19.8-3.4,21.8.5,84.1-1.5,100.1,0,2.9-5.3,4.8-8.2,11.6-1.5,3.9,4.8,8.2,7.7,8.7,5.8,0,17.4,3.4,18.9-10.6.5-5.8.5-8.2,0-9.7-1-2.4-1.5-4.8-1.5-7.3,0-16.4,0-42.6,11.6-62.4,9.7-17.4,0-32.9,2.4-38.2,1.9-5.3,6.3-9.7,8.7-32.9,2.4-20.8,12.6-37.7,13.1-69.6h0c1-1.5,1.9-2.9,2.9-2.9s2.4,1,2.9,2.9h0c0,31.9,10.6,48.8,13.1,69.6,2.9,23.2,7.3,27.6,8.7,32.9,1.9,5.3-7.7,20.8,2.4,38.2,11.6,19.8,11.6,46.4,11.6,62.4s-.5,4.8-1.5,7.3c-.5,1-1.9-.5,2.9,0,1,2.9,3.9,8.2,7.3,12.6,4.4,5.8,2.4,10.2,3.9,15.5,2.9,9.7,15,27.6,24.7,40.6,9.7,13.1,8.2,17.9,8.7,23.2.5,4.8,3.4,19.3,4.8,22.7,1.5,3.4-1,18.9,2.4,14s.5-14.5,3.9-15.5c3.4-.5.5,18.9,4.8,19.8,4.4,1,0-18.9,1.5-20.8s3.4,19.3,7.7,17.9c4.8-1-3.4-16-2.9-20.3,0-4.4,7.3,17.4,11.6,13.1,4.4-4.4-9.7-20.8-8.2-24.2,1-3.9,3.9,7.3,9.2,3.9,0-1.9-12.6-17.9-17.9-18.9h-1.9Z";
function createVisualization() {
svg.innerHTML = '';
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
clipPath.setAttribute('id', 'body-clip');
const pathForClip = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathForClip.setAttribute('d', bodyPath);
clipPath.appendChild(pathForClip);
defs.appendChild(clipPath);
svg.appendChild(defs);
const fillGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
fillGroup.setAttribute('clip-path', 'url(#body-clip)');
svg.appendChild(fillGroup);
const outline = document.createElementNS('http://www.w3.org/2000/svg', 'path');
outline.setAttribute('d', bodyPath);
outline.setAttribute('fill', 'none');
outline.setAttribute('stroke', '#333');
outline.setAttribute('stroke-width', '3');
svg.appendChild(outline);
let accumulatedHeight = 0;
const totalPercentage = Object.values(bodyData).reduce((sum, item) => sum + item.percentage, 0);
for (const [name, data] of Object.entries(bodyData)) {
if (data.percentage === 0) continue;
const height = (data.percentage / totalPercentage) * svgHeight;
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', 0);
rect.setAttribute('y', svgHeight - accumulatedHeight - height);
rect.setAttribute('width', svgWidth);
rect.setAttribute('height', height);
rect.setAttribute('fill', data.color);
rect.dataset.name = name;
rect.dataset.percentage = data.percentage;
fillGroup.appendChild(rect);
accumulatedHeight += height;
rect.addEventListener('mousemove', (e) => {
tooltip.style.opacity = '1';
const rectBox = rect.getBoundingClientRect();
const containerBox = svgContainer.getBoundingClientRect();
tooltip.style.left = `${e.clientX - containerBox.left}px`;
tooltip.style.top = `${e.clientY - containerBox.top}px`;
tooltip.innerHTML = `${name}: ${data.percentage.toFixed(1)}%`;
});
rect.addEventListener('mouseleave', () => {
tooltip.style.opacity = '0';
});
}
}
function createControls() {
slidersContainer.innerHTML = '';
legendContainer.innerHTML = '';
for (const [name, data] of Object.entries(bodyData)) {
const group = document.createElement('div');
group.className = 'control-group';
const label = document.createElement('label');
label.setAttribute('for', `${name}-slider`);
const valueSpan = document.createElement('span');
valueSpan.id = `${name}-value`;
valueSpan.textContent = `${data.percentage}%`;
label.textContent = name;
label.appendChild(valueSpan);
const slider = document.createElement('input');
slider.type = 'range';
slider.id = `${name}-slider`;
slider.min = 0;
slider.max = 100;
slider.value = data.percentage;
slider.step = 0.1;
slider.dataset.name = name;
slider.addEventListener('input', handleSliderInput);
group.appendChild(label);
group.appendChild(slider);
slidersContainer.appendChild(group);
const li = document.createElement('li');
const colorBox = document.createElement('div');
colorBox.className = 'legend-color';
colorBox.style.backgroundColor = data.color;
li.appendChild(colorBox);
const legendText = document.createElement('span');
legendText.textContent = name;
li.appendChild(legendText);
legendContainer.appendChild(li);
}
}
function handleSliderInput(e) {
const currentName = e.target.dataset.name;
const newValue = parseFloat(e.target.value);
const oldValue = bodyData[currentName].percentage;
const diff = newValue - oldValue;
bodyData[currentName].percentage = newValue;
const othersToAdjust = Object.keys(bodyData).filter(key => key !== currentName);
let totalOfOthers = othersToAdjust.reduce((sum, key) => sum + bodyData[key].percentage, 0);
if (totalOfOthers > 0) {
for (const key of othersToAdjust) {
const proportion = bodyData[key].percentage / totalOfOthers;
bodyData[key].percentage -= diff * proportion;
}
}
normalizeData();
updateControls();
createVisualization();
}
function updateControls() {
for (const name of Object.keys(bodyData)) {
const slider = document.getElementById(`${name}-slider`);
const valueSpan = document.getElementById(`${name}-value`);
if (slider) slider.value = bodyData[name].percentage;
if (valueSpan) valueSpan.textContent = `${bodyData[name].percentage.toFixed(1)}%`;
}
}
function normalizeData() {
for (const key in bodyData) {
if (bodyData[key].percentage < 0) {
bodyData[key].percentage = 0;
}
}
const total = Object.values(bodyData).reduce((sum, item) => sum + item.percentage, 0);
if (total === 0) return;
for (const key in bodyData) {
bodyData[key].percentage = (bodyData[key].percentage / total) * 100;
}
}
normalizeData();
createVisualization();
createControls();
updateControls();
4. 关键参数说明
bodyData:存储人体成分数据的对象,包含成分名称、比例和颜色。bodyPath:SVG路径数据,定义了人体轮廓。svgWidth和svgHeight:SVG画布的宽度和高度。createVisualization():负责创建或更新SVG可视化,包括绘制人体轮廓和填充不同成分。createControls():创建滑块和图例,用于调整和显示各成分比例。handleSliderInput():处理滑块输入事件,动态调整各成分比例并保持总和为100%。updateControls():更新所有滑块和数值显示。normalizeData():标准化数据,确保各成分比例总和为100%。
5. 完整代码
将上述代码整合到一个HTML文件中,完整的代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人体成分可视化</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
min-height: 100vh;
background-color: #f4f7f9;
margin: 0;
}
h1 {
color: #333;
font-weight: 300;
}
.container {
display: flex;
align-items: flex-start;
gap: 50px;
margin-top: 20px;
}
#body-visualization-container {
position: relative;
width: 250px;
height: 500px;
}
#tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.75);
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s, transform 0.2s;
transform: translate(-50%, -120%);
white-space: nowrap;
}
.controls {
width: 300px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.controls h2 {
margin-top: 0;
font-size: 18px;
font-weight: 500;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 20px;
}
.control-group {
margin-bottom: 20px;
}
.control-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-size: 14px;
}
.control-group input[type="range"] {
width: 100%;
cursor: pointer;
}
.control-group span {
float: right;
font-weight: bold;
color: #333;
}
#legend {
margin-top: 20px;
list-style: none;
padding: 0;
}
#legend li {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 14px;
}
.legend-color {
width: 18px;
height: 18px;
margin-right: 10px;
border-radius: 4px;
border: 1px solid rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h1>人体成分组成可视化</h1>
<div class="container">
<div id="body-visualization-container">
<svg id="body-visualization" width="250" height="500"></svg>
<div id="tooltip"></div>
</div>
<div class="controls">
<h2>调整成分比例</h2>
<div id="sliders"></div>
<ul id="legend"></ul>
</div>
</div>
<script>
const svg = document.getElementById('body-visualization');
const slidersContainer = document.getElementById('sliders');
const legendContainer = document.getElementById('legend');
const tooltip = document.getElementById('tooltip');
const svgContainer = document.getElementById('body-visualization-container');
const svgWidth = 250;
const svgHeight = 500;
let bodyData = {
'水': { percentage: 70, color: '#3498db' },
'蛋白质': { percentage: 15, color: '#e74c3c' },
'脂肪': { percentage: 10, color: '#f1c40f' },
'无机盐': { percentage: 5, color: '#95a5a6' }
};
const bodyPath = "M234.9,228c-7.3-1.5-14-18.9-16-26.6-1.5-7.7-5.8-27.6-18.9-44-4.8-6.3,0-10.6-8.7-34.8-7.3-19.8-5.3-37.2-19.8-42.6-12.1-4.4-26.1-2.9-31-21.3-1-3.4,0-4.8,1-8.7,2.9-5.8.5-9.7,2.4-8.7s9.2-12.6,2.4-12.6,3.4-8.2-5.3-17.9c-4.4-4.8-8.2-6.3-14.5-5.8-6.3,0-10.6,1-14.5,5.8-8.7,10.2-3.9,28.5-8.7,34.8-12.6,16.9-1,17.9-5.3,17.9-7.3,0,0,13.5,2.4,12.6,1.5-1,1,0,3.4,2.4,8.7,1.5,3.9,1.9,5.3,1,8.7-5.3,17.9-19.3,16.9-31,21.3-14.5,5.3-12.6,22.2-19.8,42.6-8.7,24.2-3.9,28.5-8.7,34.8-12.6,16.9-16.9,36.3-18.9,44-1.5,7.7-8.7,25.6-16,26.6-5.8,1-17.9,16.9-15,18.9,4.8,3.4,7.7-7.7,9.2-3.9,1,3.9-12.6,20.3-8.2,24.2,4.4,3.9,11.1-17.4,11.6-13.1,0,4.4-7.7,18.9-2.9,20.3,4.8,1,5.8-19.8,7.7-17.9,1.5,1.5-2.9,21.8,1.5,20.8s1.5-20.8,4.8-19.8c3.4,1,1,15.5,3.9,15.5s.5-11.1,2.4-14c1.5-3.4,3.9-17.4,4.8-22.7.5-4.8,0-10.2,8.7-23.2,9.7-13.1,21.8-31,24.7-40.6,1.5-5.3,0-9.7,3.9-15.5,3.4-4.4,5.8-9.7,7.3-12.6,0-1,1.5-1,2.4,0,3.9,4.8,12.6,22.2,1.5,69.6-13.5,59-5.8,88.5-5.3,98.7,0,8.2,3.4,19.3-1,40.6-1,5.3-4.4,13.1-5.3,19.8-3.4,21.8.5,84.1-1.5,100.1,0,2.9-5.3,4.8-8.2,11.6-1.5,3.9,4.8,8.2,7.7,8.7,5.8,0,17.4,3.4,18.9-10.6.5-5.8.5-8.2,0-9.7-1-2.4-1.5-4.8-1.5-7.3,0-16.4,0-42.6,11.6-62.4,9.7-17.4,0-32.9,2.4-38.2,1.9-5.3,6.3-9.7,8.7-32.9,2.4-20.8,12.6-37.7,13.1-69.6h0c1-1.5,1.9-2.9,2.9-2.9s2.4,1,2.9,2.9h0c0,31.9,10.6,48.8,13.1,69.6,2.9,23.2,7.3,27.6,8.7,32.9,1.9,5.3-7.7,20.8,2.4,38.2,11.6,19.8,11.6,46.4,11.6,62.4s-.5,4.8-1.5,7.3c-.5,1-1.9-.5,2.9,0,1,2.9,3.9,8.2,7.3,12.6,4.4,5.8,2.4,10.2,3.9,15.5,2.9,9.7,15,27.6,24.7,40.6,9.7,13.1,8.2,17.9,8.7,23.2.5,4.8,3.4,19.3,4.8,22.7,1.5,3.4-1,18.9,2.4,14s.5-14.5,3.9-15.5c3.4-.5.5,18.9,4.8,19.8,4.4,1,0-18.9,1.5-20.8s3.4,19.3,7.7,17.9c4.8-1-3.4-16-2.9-20.3,0-4.4,7.3,17.4,11.6,13.1,4.4-4.4-9.7-20.8-8.2-24.2,1-3.9,3.9,7.3,9.2,3.9,0-1.9-12.6-17.9-17.9-18.9h-1.9Z";
function createVisualization() {
svg.innerHTML = '';
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
clipPath.setAttribute('id', 'body-clip');
const pathForClip = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathForClip.setAttribute('d', bodyPath);
clipPath.appendChild(pathForClip);
defs.appendChild(clipPath);
svg.appendChild(defs);
const fillGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
fillGroup.setAttribute('clip-path', 'url(#body-clip)');
svg.appendChild(fillGroup);
const outline = document.createElementNS('http://www.w3.org/2000/svg', 'path');
outline.setAttribute('d', bodyPath);
outline.setAttribute('fill', 'none');
outline.setAttribute('stroke', '#333');
outline.setAttribute('stroke-width', '3');
svg.appendChild(outline);
let accumulatedHeight = 0;
const totalPercentage = Object.values(bodyData).reduce((sum, item) => sum + item.percentage, 0);
for (const [name, data] of Object.entries(bodyData)) {
if(data.percentage === 0) continue;
const height = (data.percentage / totalPercentage) * svgHeight;
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', 0);
rect.setAttribute('y', svgHeight - accumulatedHeight - height);
rect.setAttribute('width', svgWidth);
rect.setAttribute('height', height);
rect.setAttribute('fill', data.color);
rect.dataset.name = name;
rect.dataset.percentage = data.percentage;
fillGroup.appendChild(rect);
accumulatedHeight += height;
rect.addEventListener('mousemove', (e) => {
tooltip.style.opacity = '1';
const rectBox = rect.getBoundingClientRect();
const containerBox = svgContainer.getBoundingClientRect();
tooltip.style.left = `${e.clientX - containerBox.left}px`;
tooltip.style.top = `${e.clientY - containerBox.top}px`;
tooltip.innerHTML = `${name}: ${parseFloat(data.percentage).toFixed(1)}%`;
});
rect.addEventListener('mouseleave', () => {
tooltip.style.opacity = '0';
});
}
}
function createControls() {
slidersContainer.innerHTML = '';
legendContainer.innerHTML = '';
for (const [name, data] of Object.entries(bodyData)) {
const group = document.createElement('div');
group.className = 'control-group';
const label = document.createElement('label');
label.setAttribute('for', `${name}-slider`);
const valueSpan = document.createElement('span');
valueSpan.id = `${name}-value`;
valueSpan.textContent = `${data.percentage}%`;
label.textContent = name;
label.appendChild(valueSpan);
const slider = document.createElement('input');
slider.type = 'range';
slider.id = `${name}-slider`;
slider.min = 0;
slider.max = 100;
slider.value = data.percentage;
slider.step = 0.1;
slider.dataset.name = name;
slider.addEventListener('input', handleSliderInput);
group.appendChild(label);
group.appendChild(slider);
slidersContainer.appendChild(group);
const li = document.createElement('li');
const colorBox = document.createElement('div');
colorBox.className = 'legend-color';
colorBox.style.backgroundColor = data.color;
li.appendChild(colorBox);
const legendText = document.createElement('span');
legendText.textContent = name;
li.appendChild(legendText);
legendContainer.appendChild(li);
}
}
function handleSliderInput(e) {
const currentName = e.target.dataset.name;
const newValue = parseFloat(e.target.value);
const oldValue = bodyData[currentName].percentage;
const diff = newValue - oldValue;
bodyData[currentName].percentage = newValue;
const othersToAdjust = Object.keys(bodyData).filter(key => key !== currentName);
let totalOfOthers = othersToAdjust.reduce((sum, key) => sum + bodyData[key].percentage, 0);
if (totalOfOthers > 0) {
for (const key of othersToAdjust) {
const proportion = bodyData[key].percentage / totalOfOthers;
bodyData[key].percentage -= diff * proportion;
}
}
normalizeData();
updateControls();
createVisualization();
}
function updateControls() {
for (const name of Object.keys(bodyData)) {
const slider = document.getElementById(`${name}-slider`);
const valueSpan = document.getElementById(`${name}-value`);
if(slider) slider.value = bodyData[name].percentage;
if(valueSpan) valueSpan.textContent = `${bodyData[name].percentage.toFixed(1)}%`;
}
}
function normalizeData() {
for(const key in bodyData) {
if (bodyData[key].percentage < 0) {
bodyData[key].percentage = 0;
}
}
const total = Object.values(bodyData).reduce((sum, item) => sum + item.percentage, 0);
if (total === 0) return;
for (const key in bodyData) {
bodyData[key].percentage = (bodyData[key].percentage / total) * 100;
}
}
normalizeData();
createVisualization();
createControls();
updateControls();
</script>
</body>
</html>
四、总结
通过以上步骤,你可以创建一个交互式的人体成分可视化工具。这个工具使用SVG绘制人体轮廓并填充不同成分,通过滑块动态调整各成分比例,并显示各成分的图例和提示信息。你可以通过调整代码中的参数来改变人体图标的外观和行为。希望这个教程对你有所帮助!
以上制作的是一个最简单的一个带刻度的仪表盘,我还录制了一个更加美观的带刻度的仪表盘的视频教程,有兴趣的小伙伴可以点击查看。