根据iframe的内容调整其大小
在做邮件预览的功能时,需要将邮件的HTML内容显示出来。一开始的做法是sanitized之后直接渲染出来。但是CSS样式要尽量保留,结果就是这些样式连外面不属于邮件的部分也影响了。
于是把邮件的内容放到一个iframe里面去,隔离它的样式。而iframe一般要写死高度和宽度,这样如果邮件内容过长过宽,就会出现滚动条。理想的情况是根据iframe的内容调整其大小。
这里记录一下我是怎么做的,直接上代码:
<template>
<iframe
:id="iframe_id"
:src="iframe.src"
:style="iframe.style"
@load="onIframeLoaded"
>
</iframe>
</template>
<script>
import HtmlSanitizer from '@/assets/HtmlSanitizer.js'
export default {
name: "vue-iframe",
data() {
return {
iframe: {
src: '',
style: {}
},
bloburl: ''
}
},
props: {
id: {default: 'default'},
raw_html: {default: ''},
url: {default: ''},
height: {default: ''},
width: {default: '100%'},
innerStyle: {type: String}
},
computed: {
iframe_id: function() {
return 'iframe-' + this.id + '-' + Math.floor(Math.random() * 10E8)
},
isHeightSet: function() {
return !!this.height && !!this.height.length
},
// 判断传进来的url是否同源
isSameOrigin: function() {
if (!this.url || !this.url.length) {
return true
}
let loc = new URL(window.location.href)
let url = new URL(this.url)
return loc.origin === url.origin
}
},
watch: {
raw_html: function(val, oldVal) {
this.initIframe()
},
url: function(val, oldVal) {
this.initIframe()
}
},
methods: {
initIframe() {
this.iframe.style.height = '0' // 重置iframe高度,否则iframe不会缩小
if (this.url && this.url.length) {
this.setIframeUrl(this.url)
}
else {
let html = HtmlSanitizer.SanitizeHtml(this.raw_html)
this.setIframeContent(html)
}
},
setIframeUrl(url) {
this.iframe.src = url
},
setIframeContent(content) {
let ua = window.navigator.userAgent
if (ua.indexOf('Trident/') > -1) { // IE
let doc = document.getElementById(this.iframe_id).contentWindow.document
doc.open().write(content)
doc.close()
}
else {
window.URL.revokeObjectURL(this.bloburl)
let blob = new Blob(['\uFEFF', content], {type: 'text/html'})
this.bloburl = window.URL.createObjectURL(blob)
this.iframe.src = this.bloburl
}
},
onIframeLoaded() {
this.setDocStyle()
this.setIframeHeight()
this.$emit('loaded', this.iframe.style.height)
// 设置iframe的高度可能会让父元素出现滚动条,反过来影响iframe内容的尺寸
// 监听iframe contentWindow的resize事件重新计算内容高度
let iframe = document.getElementById(this.iframe_id)
iframe.contentWindow.addEventListener('resize', this.setIframeHeight)
},
setIframeHeight() {
if (this.isHeightSet) {
this.iframe.style.height = this.height
}
else if (this.isSameOrigin) {
let doc = document.getElementById(this.iframe_id).contentWindow.document
let height = this.getDocHeight(doc)
if (height === 0) { // 有时高度会计算错误
height = 40
}
else if (doc.body && doc.body.clientWidth < doc.body.scrollWidth) {
height += 17 // 滚动条的高度
}
this.iframe.style.height = height + 'px'
}
else {
this.iframe.style.height = '100%'
}
// this.$el.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'})
},
getDocHeight(doc) {
doc = doc || document
let body = doc.body
let html = doc.documentElement
if (!body || !html) return 0
let height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight)
return height
},
// 设置iframe内容的基本样式
setDocStyle() {
if (this.isSameOrigin) {
let doc = document.getElementById(this.iframe_id).contentWindow.document
if (doc) {
doc.body.style.margin = '0'
doc.body.style.backgroundColor = 'transparent'
let css = 'body {font-family: Calibri, Arial, Helvetica, Hiragino Sans GB, Microsoft YaHei, sans-serif; font-size: 14px;}'
if (this.innerStyle) css += this.innerStyle
let style = document.createElement('style')
style.appendChild(document.createTextNode(css))
doc.head.insertBefore(style, doc.head.firstChild)
}
}
}
},
created() {
this.iframe.style = {
position: 'relative',
height: this.height,
width: this.width,
border: 0
}
},
mounted() {
this.initIframe()
}
}
</script>
可以看到,这里封装的组件可以处理两种情况,一种是传进来url,另一种是传进来裸html。基本的思路是等iframe加载完成之后,计算一下里面的高度和宽度,然后相应地设置iframe的大小。
用法:
<vue-iframe url="xxx"></vue-iframe>
<vue-iframe raw_html="<html>...</html>"></vue-iframe>
可以看到,经过封装后的组件使用非常简单,只要把显示的内容传进去就可以了。
最后就是这个方法的缺点,那就是加载的url必须是同源的,否则获取不到内容的大小。 不过由于需求是显示裸html string,这个缺点问题不大。