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>
我在理解 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>