如何修改D3代码实现柱状图滚动触发向下飞入动画?
问题描述
我用D3.js制作柱状图,当前柱子会从左上角斜向飞入(受D3坐标系影响),想要实现柱子直接向下飞入的动画效果,并且仅当用户滚动到该元素所在区域时才触发动画(页面较长,用户需要滚动很久才能到达)。以下是我的代码:
let data = [ {"color": "Blue", "plotValue": 11}, {"color": "Red", "plotValue": 20}, {"color": "Orange", "plotValue": 21}, {"color": "Purple", "plotValue": 30}, {"color": "Green", "plotValue": 35}, {"color": "Violet", "plotValue": 40}, ] vis.margin = {top: 70, right: 90, bottom: 90, left: 80}; vis.width = 800 - vis.margin.left - vis.margin.right; // initialize the drawing area vis.svg = d3.select('#' + vis.parentElement).append('svg') .attr('width', vis.width + vis.margin.left + vis.margin.right) .attr('height', vis.height + vis.margin.top + vis.margin.bottom) .append('g') .attr('transform', `translate (${vis.margin.left}, ${vis.margin.top})`); // scales and axes vis.xScale = d3.scaleBand() .range( [ 0, vis.width ] ) .padding(0.4); vis.yScale = d3.scaleLinear() .range( [ vis.height, 0 ] ); vis.xAxis = d3.axisBottom() .scale(vis.xScale); vis.yAxis = d3.axisLeft() .scale(vis.yScale); // add chart title vis.svg.append('g') .attr('class', 'title bar-chart-title') .append('text') .text(vis.chartTitle) .attr('transform', `translate(${vis.width / 2 + 45}, -50)`) .attr('text-anchor', 'middle'); // tooltip vis.tooltip = d3.select('body').append('div') .attr('class', 'tooltip') .attr('id', 'barChartTooltip'); // create the axis groups vis.xAxisGroup = vis.svg.append('g') .attr('class', 'x-axis axis') .attr('transform', 'translate(0, ' + vis.height + ')'); vis.yAxisGroup = vis.svg.append('g') .attr('class', 'y-axis axis'); let vis = this; // update the domains vis.xScale.domain(vis.data.map(function (d) { return d.color; })); vis.yScale.domain( [ 0, 100 ] ); // draw the bars vis.bars = vis.svg.selectAll('.bar') .data(vis.data) vis.bars.exit().remove(); vis.bars .enter() .append('rect') .attr('class', 'bar') .on('mouseover', function(event, d) { d3.select(this) .attr('stroke-width', '2px') .attr('stroke', 'grey') .attr('fill', 'blue') vis.tooltip .style('opacity', 1) .style('left', event.pageX + 20 + 'px') .style('top', event.pageY + 'px') .html(` <div style='border: thin solid grey; border-radius: 5px; background: lightgrey; padding: 20px'> <h3>${d.color}</h3> <h4> ${100- d.plotValue.toLocaleString()}% decline </h4> </div> `) }) .on('mouseout', function(event, d) { d3.select(this) .attr('stroke-width', 1) .attr('stroke', '#456983') .attr('fill', function(d) { return 'red' }) vis.tooltip .style('opacity', 0) .style('left', 0) .style('top', 0) .html(``); }) .merge(vis.bars) .transition() .duration(1200) .attr('x', d => vis.xScale(d.color) ) .attr('y', d => vis.yScale(d.plotValue) ) .attr('width', vis.xScale.bandwidth() ) .attr('height', function(d) { return vis.height - vis.yScale(d.plotValue); }) .attr('fill', 'red') // .attr('stroke', 'grey'); // add the axes vis.xAxisGroup .transition() .duration(500) .style('font-size', '15px') .style('color', 'blue') .call(d3.axisBottom((vis.xScale))) .selectAll('text') .attr('y', 30) .attr('x', -35) .attr('dy', '.35em') .attr('transform', 'rotate(-30)') ; vis.yAxisGroup .transition() .duration(500) .style('font-size', '15px') .style('color', 'blue') .call(d3.axisLeft(vis.yScale)); vis.text = vis.svg.selectAll('.text') .data(vis.data) vis.text .enter() .append('text') .attr('class', 'text') .attr('text-anchor', 'middle') .attr('x', d => vis.xScale(d.color) + 27) .attr('y', d => vis.yScale(d.plotValue) - 10) .text( function (d) { return '-' + (100 - d.plotValue) + '%'; })
解决方案
一、修改柱子动画为向下飞入
当前动画同时过渡x、y、height属性导致斜向运动,要实现向下飞入,需让柱子初始状态固定x位置,从顶部(y=0)、高度为0的状态开始,仅过渡y和height属性:
关键修改代码:
// 绘制柱子 vis.bars = vis.svg.selectAll('.bar') .data(vis.data) vis.bars.exit().remove(); // 初始设置柱子状态:x位置正确,y在顶部,高度为0 const barsEnter = vis.bars .enter() .append('rect') .attr('class', 'bar') .attr('x', d => vis.xScale(d.color)) .attr('y', 0) .attr('width', vis.xScale.bandwidth()) .attr('height', 0) .attr('fill', 'red') // 保留原mouseover/mouseout逻辑... // 合并后仅过渡y和height,实现向下飞入 barsEnter.merge(vis.bars) .transition() .duration(1200) .attr('y', d => vis.yScale(d.plotValue)) .attr('height', function(d) { return vis.height - vis.yScale(d.plotValue); })
二、添加滚动触发动画逻辑
通过监听页面滚动,判断图表元素是否进入视口,仅在进入时执行动画:
关键修改代码:
// 封装动画函数 function animateBars() { barsEnter.merge(vis.bars) .transition() .duration(1200) .attr('y', d => vis.yScale(d.plotValue)) .attr('height', function(d) { return vis.height - vis.yScale(d.plotValue); }) // 可选:让数值标签和柱子同步动画 vis.text.merge(vis.text) .transition() .duration(1200) .attr('y', d => vis.yScale(d.plotValue) - 10) .attr('opacity', 1) } // 获取图表容器元素 const chartContainer = document.getElementById(vis.parentElement); // 检查元素是否在视口内 function checkScroll() { const rect = chartContainer.getBoundingClientRect(); const isInView = rect.top < window.innerHeight && rect.bottom > 0; if (isInView) { animateBars(); // 仅执行一次动画,移除监听 window.removeEventListener('scroll', checkScroll); } } // 初始检查(页面加载时元素可能已在视口) checkScroll(); // 添加滚动监听 window.addEventListener('scroll', checkScroll);
额外调整:数值标签初始状态
如果要让数值标签和柱子同步出现,需设置初始隐藏状态:
vis.text = vis.svg.selectAll('.text') .data(vis.data) vis.text .enter() .append('text') .attr('class', 'text') .attr('text-anchor', 'middle') .attr('x', d => vis.xScale(d.color) + 27) .attr('y', 0 - 10) // 初始在顶部 .attr('opacity', 0) // 初始隐藏 .text( function (d) { return '-' + (100 - d.plotValue) + '%'; })
内容的提问来源于stack exchange,提问作者equanimity
相关产品推荐
相关产品推荐

