js实现漏斗
使用 Canvas 绘制漏斗图
Canvas 提供了灵活的绘图能力,适合实现自定义漏斗效果。以下是一个基础实现示例:

const canvas = document.getElementById('funnelCanvas');
const ctx = canvas.getContext('2d');
function drawFunnel(data) {
const width = canvas.width;
const height = canvas.height;
const segmentCount = data.length;
const segmentHeight = height / segmentCount;
data.forEach((item, index) => {
const ratio = 1 - (index * 0.15); // 每层宽度递减比例
const segmentWidth = width * ratio;
const x = (width - segmentWidth) / 2;
const y = index * segmentHeight;
ctx.fillStyle = `hsl(${index * 30}, 70%, 60%)`;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + segmentWidth, y);
ctx.lineTo(x + segmentWidth * 0.9, y + segmentHeight);
ctx.lineTo(x + segmentWidth * 0.1, y + segmentHeight);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText(item.label, width/2, y + segmentHeight/2 + 5);
});
}
drawFunnel([
{ label: 'Visits', value: 1000 },
{ label: 'Signups', value: 800 },
{ label: 'Purchases', value: 400 }
]);
使用 SVG 创建漏斗图
SVG 方案更适合需要动态交互的场景:

function createSVGFunnel(data, containerId) {
const container = document.getElementById(containerId);
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute('viewBox', '0 0 200 300');
const total = Math.max(...data.map(d => d.value));
const segmentHeight = 300 / data.length;
data.forEach((item, i) => {
const width = 200 * (item.value / total);
const y = i * segmentHeight;
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
const d = `
M ${100 - width/2} ${y}
L ${100 + width/2} ${y}
L ${100 + width/2 * 0.9} ${y + segmentHeight}
L ${100 - width/2 * 0.9} ${y + segmentHeight}
Z
`;
path.setAttribute('d', d);
path.setAttribute('fill', `hsl(${i * 40}, 80%, 65%)`);
svg.appendChild(path);
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute('x', '100');
text.setAttribute('y', y + segmentHeight/2);
text.setAttribute('fill', 'white');
text.setAttribute('text-anchor', 'middle');
text.textContent = `${item.label}: ${item.value}`;
svg.appendChild(text);
});
container.appendChild(svg);
}
使用 D3.js 高级方案
对于复杂数据可视化,D3.js 提供了更专业的解决方案:
import * as d3 from 'd3';
function renderD3Funnel(data, container) {
const width = 600;
const height = 400;
const margin = { top: 20, right: 20, bottom: 60, left: 20 };
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const maxValue = d3.max(data, d => d.value);
const yScale = d3.scaleLinear()
.domain([0, data.length])
.range([margin.top, height - margin.bottom]);
const xScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, width - margin.left - margin.right]);
const color = d3.scaleOrdinal()
.domain(data.map((d,i) => i))
.range(d3.schemeTableau10);
const funnel = svg.append('g')
.attr('transform', `translate(${margin.left},0)`);
data.forEach((d, i) => {
const currentWidth = xScale(d.value);
const nextWidth = i < data.length - 1 ? xScale(data[i+1].value) : 0;
funnel.append('path')
.attr('d', `
M ${(width - currentWidth)/2} ${yScale(i)}
L ${(width + currentWidth)/2} ${yScale(i)}
L ${(width + nextWidth)/2} ${yScale(i+1)}
L ${(width - nextWidth)/2} ${yScale(i+1)}
Z
`)
.attr('fill', color(i))
.attr('stroke', '#fff');
funnel.append('text')
.attr('x', width/2)
.attr('y', yScale(i) + (yScale(1) - yScale(0))/2)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.text(`${d.label} (${d.value})`);
});
}
响应式设计注意事项
实现响应式漏斗图需要考虑以下要点:
- 使用百分比宽度而非固定像素值
- 添加 resize 事件监听器重新计算尺寸
- 设置适当的 viewBox 保持 SVG 比例
- 使用 CSS media queries 调整文字大小
window.addEventListener('resize', function() {
const containerWidth = document.getElementById('chartContainer').clientWidth;
const aspectRatio = 2/3; // 宽高比
const newHeight = containerWidth * aspectRatio;
d3.select('#funnelSVG')
.attr('width', containerWidth)
.attr('height', newHeight);
});
以上方案可根据具体需求选择,Canvas 适合性能敏感场景,SVG 适合需要交互的场景,D3.js 则提供最完整的数据可视化功能。实际项目中可结合 CSS 动画和过渡效果增强用户体验。






