在 Vuejs 中计算 DOM 元素之间距离的最佳方法是什么?
What’s the best approach to compute distance between DOM Elements in Vuejs?
我正在构建一个从数据生成 pdf 文件的工具,我需要构建两种格式:105mm * 148mm 和 105mm * 210mm。所以我得到了整个文档,现在是我插入分页符的时候了。我用一个简单的 class:
.page-break { display: block; page-break-before: always; }
现在我必须将这个 class 插入到我的 v-for 循环中。所以一个基本的想法是计算一个区间,比如每个索引都是 6 的倍数,我插入一个。但这不是最好的方法,我想在内容超过90mm时插入一个中断。
为此,我想计算 2 个断裂之间的距离,如果距离接近 90 毫米,则插入一个新断裂。但是,我找不到访问我的动态 DOM 元素的方法...
所以问题很简单:如何计算这个距离?或者如果有更好的方法来实现我的目标,我可以改进什么?
我相信您在每个 v-for
中添加了一个 div/component,并且您可以为每个 div 添加一个唯一的 ID。现在下面的方法可以让你在px
中得到一个div的高度,你将有一些方法将px
转换为mm
。
如果您使用的是jquery,您可以
$('#nthDiv').height();
如果没有,您可以执行以下操作:
document.getElementById('#nthDiv').clientHeight;
document.getElementById('#nthDiv').offsetHeight;
如果您有以下代码:
<div v-for="(item, index) in items" :class="{'page-break': isBreakNeeded(index)}" :id="index + 'thDiv'">
///Your code
</div>
您需要添加以下方法:
isBreakNeeded (index) {
var height = 0
do {
index -= 1;
height += document.getElementById('#' + index + 'thDiv').offsetHeight;
} while (index >= 0 || pageBreakAdded[index] == true)
if(height > threshold){
pageBreakAdded[index] = true
return true
}
else{
return false
}
}
您还需要在 vue 元素的数据属性中添加以下散列,它将跟踪您添加分页符的索引:
pageBreakAdded: {}
所以,我尝试了这种方法:
- 在我的循环模板中插入一个
page-break
(div v-for each data text element
)
- select渲染后的所有
page-break
- 使用
getBoundingClientRect()
计算
- 遍历 selection
- 获取当前中断与下一个中断之间的距离
- 如果超出了可接受的范围,就将他从DOM中删除。
这个方法可行,但它非常丑陋,很难计算出一个好的范围,而且非常昂贵!对于小数据集还可以,但是对于整本书来说,它太长了...
这是第一次尝试(使用 Vue
模板)
<template>
<div class="content">
<div class="pages">
<div class="page-footer">
<div class="page-break"></div>
</div>
<div class="dynamic">
<div v-for="(entry, index) in conv" class="row">
<div v-if="entry.msgs[0].value === 'break'">
<span class="date">{{entry.msgs[0].metadate}}</span>
</div>
<div v-else>
<span class="author">{{ entry.user }}</span>
<span class="hour">{{ entry.msgs[0].metahour }}</span>
<div class="phrases">
<span v-for="msg in entry.msgs">
{{ msg.value }}
</span>
</div>
</div>
<div class="page-footer" :data-base="index">
<span class="page-number" :data-base="index">{{ pageNumberFor(index) }}</span>
<div class="page-break"></div>
</div>
</div>
</div>
<div class="page-footer" data-base="end">
<span class="page-number" data-base="end">{{ pageNumberFor('end') }}</span>
<div class="page-break"></div>
</div>
</div>
</div>
</template>
<script>
import und from 'underscore'
export default {
name: "Data",
data () {
return {
conv: null,
cleaned: false,
pageNumbers: {}
}
},
computed: {
mmScale: () => 0.264583333,
heightBreak: () => 210
},
mounted () {
this.$http.get('static/data_sorted_backup.json').then(
// this.$http.get('static/data_sorted.json').then(
(response) => {
this.conv = this.groupBy(response.data)
},
(response) => {
console.log('error');
}
)
},
updated () {
console.log('updated');
if(!this.cleaned) this.cleanPageBreak()
},
methods: {
groupBy (json) {
let result = []
result.push(json[0])
let punc = new RegExp(/[\?\.\!\;\(\)]?$/, 'ig')
for (var i = 1; i < json.length; i++) {
let val = json[i].msgs[0].value
val = val.charAt(0).toUpperCase() + val.slice(1);
if( punc.test(val) === false ) val += '.'
json[i].msgs[0].value = val
let len = result[result.length -1].msgs.length
// if it's not the same day
let origin = result[result.length -1].msgs[len - 1].metadate
let new_entry = json[i].msgs[0].metadate
// console.log(i, origin, new_entry);
if( origin !== new_entry ){
result.push({
msgs: [{
value:"break",
metadate: json[i].msgs[0].metadate
}]
})
i--
continue;
}
// if the previous author is the same
if(result[result.length -1].user === json[i].user){
result[result.length -1].msgs.push({
value: json[i].msgs[0].value,
metadate: json[i].msgs[0].metadate,
metahour: json[i].msgs[0].metahour
})
}else{
result.push(json[i])
}
}
return result
},
cleanPageBreak () {
console.log('cleanPageBreak');
let breaks = this.$el.querySelectorAll('.page-footer')
let distance
let enough
let previousTop = breaks[0].getBoundingClientRect().top
let seuil = this.heightBreak * 0.85
console.log(breaks.length, seuil);
for (let i = 1; i < breaks.length; ++i) {
console.log(i);
distance = (breaks[i].getBoundingClientRect().top - previousTop) * this.mmScale
enough = distance < this.heightBreak && distance >= seuil
if (enough) {
previousTop = breaks[i].getBoundingClientRect().top
} else if(i != breaks.length -1) {
breaks[i].remove()
}
}
this.cleaned = true
this.mapNumbers()
},
mapNumbers () {
console.log('mapNumbers');
let numbers = Array.from(this.$el.querySelectorAll('.page-number'))
numbers.map( (elem, index) => {
this.pageNumbers[elem.dataset.base] = {}
this.pageNumbers[elem.dataset.base].index = index + 1
})
},
pageNumberFor (index) {
if(this.pageNumbers[index]) return this.pageNumbers[index].index
return 0
}
}
}
</script>
<style lang="scss">
@import url('https://fonts.googleapis.com/css?family=Abel|Abhaya+Libre');
.page-footer{
position: relative;
margin: 12mm 0;
// border: 1px dotted black;
.page-break {
display: block;
page-break-before: always;
}
.page-number {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
top: -28mm;
}
}
.content {
padding: 0 16mm 0 10mm;
font-family: 'Abhaya Libre', serif;
.pages{
position: relative;
}
}
.row {
margin: 0;
font-size: 0;
span{
font-size: 13px;
}
span:first-letter {
text-transform: uppercase;
}
.date, .hour {
font-family: 'Abel', sans-serif;
}
.date {
font-size: 15px;
text-align: center;
display: block;
padding-top: 10mm;
padding-bottom: 2mm;
}
.author{
display: block;
text-transform: uppercase;
text-align: center;
}
.hour {
margin-bottom: 3mm;
margin-top: -2px;
display: block;
text-align: center;
font-size: 9px;
}
.phrases{
text-indent:5mm;
}
}
.row + .row {
margin-top: 5mm;
}
</style>
我正在构建一个从数据生成 pdf 文件的工具,我需要构建两种格式:105mm * 148mm 和 105mm * 210mm。所以我得到了整个文档,现在是我插入分页符的时候了。我用一个简单的 class:
.page-break { display: block; page-break-before: always; }
现在我必须将这个 class 插入到我的 v-for 循环中。所以一个基本的想法是计算一个区间,比如每个索引都是 6 的倍数,我插入一个。但这不是最好的方法,我想在内容超过90mm时插入一个中断。
为此,我想计算 2 个断裂之间的距离,如果距离接近 90 毫米,则插入一个新断裂。但是,我找不到访问我的动态 DOM 元素的方法...
所以问题很简单:如何计算这个距离?或者如果有更好的方法来实现我的目标,我可以改进什么?
我相信您在每个 v-for
中添加了一个 div/component,并且您可以为每个 div 添加一个唯一的 ID。现在下面的方法可以让你在px
中得到一个div的高度,你将有一些方法将px
转换为mm
。
如果您使用的是jquery,您可以
$('#nthDiv').height();
如果没有,您可以执行以下操作:
document.getElementById('#nthDiv').clientHeight;
document.getElementById('#nthDiv').offsetHeight;
如果您有以下代码:
<div v-for="(item, index) in items" :class="{'page-break': isBreakNeeded(index)}" :id="index + 'thDiv'">
///Your code
</div>
您需要添加以下方法:
isBreakNeeded (index) {
var height = 0
do {
index -= 1;
height += document.getElementById('#' + index + 'thDiv').offsetHeight;
} while (index >= 0 || pageBreakAdded[index] == true)
if(height > threshold){
pageBreakAdded[index] = true
return true
}
else{
return false
}
}
您还需要在 vue 元素的数据属性中添加以下散列,它将跟踪您添加分页符的索引:
pageBreakAdded: {}
所以,我尝试了这种方法:
- 在我的循环模板中插入一个
page-break
(div v-for each data text element
) - select渲染后的所有
page-break
- 使用
getBoundingClientRect()
计算- 遍历 selection
- 获取当前中断与下一个中断之间的距离
- 如果超出了可接受的范围,就将他从DOM中删除。
这个方法可行,但它非常丑陋,很难计算出一个好的范围,而且非常昂贵!对于小数据集还可以,但是对于整本书来说,它太长了...
这是第一次尝试(使用 Vue
模板)
<template>
<div class="content">
<div class="pages">
<div class="page-footer">
<div class="page-break"></div>
</div>
<div class="dynamic">
<div v-for="(entry, index) in conv" class="row">
<div v-if="entry.msgs[0].value === 'break'">
<span class="date">{{entry.msgs[0].metadate}}</span>
</div>
<div v-else>
<span class="author">{{ entry.user }}</span>
<span class="hour">{{ entry.msgs[0].metahour }}</span>
<div class="phrases">
<span v-for="msg in entry.msgs">
{{ msg.value }}
</span>
</div>
</div>
<div class="page-footer" :data-base="index">
<span class="page-number" :data-base="index">{{ pageNumberFor(index) }}</span>
<div class="page-break"></div>
</div>
</div>
</div>
<div class="page-footer" data-base="end">
<span class="page-number" data-base="end">{{ pageNumberFor('end') }}</span>
<div class="page-break"></div>
</div>
</div>
</div>
</template>
<script>
import und from 'underscore'
export default {
name: "Data",
data () {
return {
conv: null,
cleaned: false,
pageNumbers: {}
}
},
computed: {
mmScale: () => 0.264583333,
heightBreak: () => 210
},
mounted () {
this.$http.get('static/data_sorted_backup.json').then(
// this.$http.get('static/data_sorted.json').then(
(response) => {
this.conv = this.groupBy(response.data)
},
(response) => {
console.log('error');
}
)
},
updated () {
console.log('updated');
if(!this.cleaned) this.cleanPageBreak()
},
methods: {
groupBy (json) {
let result = []
result.push(json[0])
let punc = new RegExp(/[\?\.\!\;\(\)]?$/, 'ig')
for (var i = 1; i < json.length; i++) {
let val = json[i].msgs[0].value
val = val.charAt(0).toUpperCase() + val.slice(1);
if( punc.test(val) === false ) val += '.'
json[i].msgs[0].value = val
let len = result[result.length -1].msgs.length
// if it's not the same day
let origin = result[result.length -1].msgs[len - 1].metadate
let new_entry = json[i].msgs[0].metadate
// console.log(i, origin, new_entry);
if( origin !== new_entry ){
result.push({
msgs: [{
value:"break",
metadate: json[i].msgs[0].metadate
}]
})
i--
continue;
}
// if the previous author is the same
if(result[result.length -1].user === json[i].user){
result[result.length -1].msgs.push({
value: json[i].msgs[0].value,
metadate: json[i].msgs[0].metadate,
metahour: json[i].msgs[0].metahour
})
}else{
result.push(json[i])
}
}
return result
},
cleanPageBreak () {
console.log('cleanPageBreak');
let breaks = this.$el.querySelectorAll('.page-footer')
let distance
let enough
let previousTop = breaks[0].getBoundingClientRect().top
let seuil = this.heightBreak * 0.85
console.log(breaks.length, seuil);
for (let i = 1; i < breaks.length; ++i) {
console.log(i);
distance = (breaks[i].getBoundingClientRect().top - previousTop) * this.mmScale
enough = distance < this.heightBreak && distance >= seuil
if (enough) {
previousTop = breaks[i].getBoundingClientRect().top
} else if(i != breaks.length -1) {
breaks[i].remove()
}
}
this.cleaned = true
this.mapNumbers()
},
mapNumbers () {
console.log('mapNumbers');
let numbers = Array.from(this.$el.querySelectorAll('.page-number'))
numbers.map( (elem, index) => {
this.pageNumbers[elem.dataset.base] = {}
this.pageNumbers[elem.dataset.base].index = index + 1
})
},
pageNumberFor (index) {
if(this.pageNumbers[index]) return this.pageNumbers[index].index
return 0
}
}
}
</script>
<style lang="scss">
@import url('https://fonts.googleapis.com/css?family=Abel|Abhaya+Libre');
.page-footer{
position: relative;
margin: 12mm 0;
// border: 1px dotted black;
.page-break {
display: block;
page-break-before: always;
}
.page-number {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
top: -28mm;
}
}
.content {
padding: 0 16mm 0 10mm;
font-family: 'Abhaya Libre', serif;
.pages{
position: relative;
}
}
.row {
margin: 0;
font-size: 0;
span{
font-size: 13px;
}
span:first-letter {
text-transform: uppercase;
}
.date, .hour {
font-family: 'Abel', sans-serif;
}
.date {
font-size: 15px;
text-align: center;
display: block;
padding-top: 10mm;
padding-bottom: 2mm;
}
.author{
display: block;
text-transform: uppercase;
text-align: center;
}
.hour {
margin-bottom: 3mm;
margin-top: -2px;
display: block;
text-align: center;
font-size: 9px;
}
.phrases{
text-indent:5mm;
}
}
.row + .row {
margin-top: 5mm;
}
</style>