d3.js 使用过渡后边界框值错误

d3.js wrong boundingbox-values after using a transition

我在理解 d3 的操作顺序时遇到问题。js/javascript 在将转换方法与事件结合使用时。为了说明我的普遍困惑,我创建了一个 div,它会在点击它后改变它的高度,还有一个段落使用它的边界框来获取它的文本值的高度:

dataArr = [{data:50, id:'a', val : true}]

function update(){
    divs = d3.select('body').selectAll('div')
        .data(dataArr)
        .join('div')
        .text("click me")
        .style('background-color','red')
        .attr('id', d=> d.id)
        .on('click', function(_,d){
            let heightVal = 20
            if(d.val){
                heightVal = 50
                d.val = false
            }
            else{
                d.val = true
            }
            d3.select(this).transition().duration(200).style('height',heightVal + 'px').text(heightVal) 
            update()
        })
        
    d3.select('body').selectAll('p').data([true]).join('p').text(function(d){
        return 'height: ' + d3.select('#a').node().getBoundingClientRect().height
    })
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="UTF-8">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
</body>

根据我的理解,在简单单击后,段落的文本应该在转换完成后具有值,但是该值的文本来自转换开始之前。谁能帮我理解为什么会这样以及如何解决这个问题?

您的更新函数启动了一个转换,它开始与下一个代码块同时(异步)运行,该代码块通过测量其呈现的边界框来确定 div 的高度那个时候。

由于过渡还没有完成,它实际上才刚刚开始,你不会得到过渡完成后元素未来的大小,而是开始时的大小,即原始大小, 或非常接近原始值。这就是为什么“值的文本来自转换开始之前”——您在转换开始时测量了大小(如果您的计算机速度非常慢,您可能会在转换早期而不是开始时看到值).

如果要等到过渡结束再测量尺寸,可以使用transition.on("end", function() { ... }):

dataArr = [{data:50, id:'a', val : true}]

function update(){
    divs = d3.select('body').selectAll('div')
        .data(dataArr)
        .join('div')
        .text("click me")
        .style('background-color','red')
        .attr('id', d=> d.id)
        .on('click', function(_,d){
            let heightVal = 20
            if(d.val){
                heightVal = 50
                d.val = false
            }
            else{
                d.val = true
            }
            d3.select(this).transition().duration(200).style('height',heightVal + 'px').text(heightVal) 
             .on("end", function() {
                d3.select('body').selectAll('p').data([true]).join('p').text(function(d){
                  return 'height: ' + d3.select('#a').node().getBoundingClientRect().height
                })
            })
            update()
        })
       
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="UTF-8">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
</body>

当然,在转换完成之前,这不会更新文本。如果您想要在过渡完成之前获得最终值,访问它的最简单方法是直接使用提供给过渡的值:heightVal 并将文本值设置为此,而不是使用 getBoundingClientRect。由于 getBoundingClientRect 会及时测量给定时刻的尺寸,因此它不能用于测量未来某个时间点的尺寸。

为了比较,框中的文本会立即更新,因为它使用 transition.text(),它将“对于每个选定的元素,将文本内容设置为指定的目标值过渡开始 (docs)."

您可以使框中的文本转换为:

dataArr = [{data:50, id:'a', val : true}]

function update(){
    divs = d3.select('body').selectAll('div')
        .data(dataArr)
        .join('div')
        .text("click me")
        .style('background-color','red')
        .attr('id', d=> d.id)
        .on('click', function(_,d){
            let heightVal = 20
            if(d.val){
                heightVal = 50
                d.val = false
            }
            else{
                d.val = true
            }
            d3.select(this).transition().duration(200).style('height',heightVal + 'px').textTween(()=>d3.interpolateRound(heightVal == 20 ? 50 : 20, heightVal))
             .on("end", function() {
                d3.select('body').selectAll('p').data([true]).join('p').text(function(d){
                  return 'height: ' + d3.select('#a').node().getBoundingClientRect().height
                })
            })
            update()
        })
       
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="UTF-8">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
</body>

您还可以在转换期间使用 getBoundingClientRect 获取框外文本的大小:

dataArr = [{data:50, id:'a', val : true}]

function update(){
    divs = d3.select('body').selectAll('div')
        .data(dataArr)
        .join('div')
        .text("click me")
        .style('background-color','red')
        .attr('id', d=> d.id)
        .on('click', function(_,d){
            let heightVal = 20
            if(d.val){
                heightVal = 50
                d.val = false
            }
            else{
                d.val = true
            }
            
            d3.select(this)
              .transition()
              .duration(200)
              .style('height',heightVal + 'px')
              .attrTween("anything", function() { 
                return function() {
                   d3.select('body')
                       .selectAll('p')
                       .data([true])
                       .join('p')
                       .text(function(d){
                       return 'height: ' + d3.select('#a').node().getBoundingClientRect().height
                   })
                }
            })
            .duration(2000);
            
            update()
        })
       
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="UTF-8">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
</body>