大家好,我是瑶琴呀!
开门见山,这次需要实现的功能: iframe内嵌一个链接,现在需要实现让用户强制滚动到底部阅读后才可以进行下一步操作。 1.iframe是封装作为子组件,2.父组件调用iframe子组件监听滚动.
实现要点:
1.拿到iframe的DOM元素,并给iframe元素绑定滚动事件,
2.监听iframe的滚动不可以跨域,需发到测试环境下测试,本地无法调试成功
子组件实现:components\Iframe.vue
<template>
<div>
<iframe
ref="iframe"
:src="url"
@load="handleLoad"
frameborder="0"
scrolling="auto"
style="width: 100%; height: 100%"
/>
</div>
</template>
<script>
export default {
name: "iFrame",
props: {
url: "",
},
data() {
return {};
},
methods: {
handleLoad(e) {
this.$emit('mounted', this.$refs.iframe); // 把真实 iframe DOM 传给子组件
}
}
};
</script>
父组件调用多个iframe,因为不止一个文档链接需要完整阅读
<div class="content">
<Iframe
ref="iframe"
v-for="(item, index) in data"
:key="index"
v-show="active == index"
:src="item.link"
style="width: 100%; height: 100%"
@mounted="(iframeEl) => onIframeMounted(index, iframeEl)"
></Iframe>
</div>
数据定义:
data() {
return {
active: 0,
data: [],
currentIframeRef: null, // 保存当前 iframe 引用
iframeElements: [], // 存储所有 iframe DOM 元素
isReachBottom: false, // 标识是否拉到底部
readOverArr: [],// 存储已读的 iframe
};
},
父组件存储iframe 的DOM,这一步很重要,因为我们的iframe是以子组件引入的,没法通过父组件表层拿到对应DOM
// 接收iframe子组件传过来的iframe引用,并保存下来
onIframeMounted(index, iframeEl) {
// 保存每个 iframe DOM 元素
this.iframeElements[index] = iframeEl;
this.$nextTick(() => {
this.bindIframeScroll();
});
},
给 iframe 绑定监听滚动事件,this.active 是用来找到对应的 iframe 的。
bindIframeScroll() {
let self = this;
// 移除之前的监听
if (this.currentIframeRef && this.currentIframeRef.contentWindow) {
try {
this.currentIframeRef.contentWindow.removeEventListener("scroll", this.handleIframeScroll);
} catch (e) {}
}
const iframeElement = this.iframeElements[this.active];
if (!iframeElement) return;
this.currentIframeRef = iframeElement;
try {
// ✅ 监听 contentWindow.scroll
const contentWindow = iframeElement.contentWindow;
// 这一步我直接写this.iframeElements[this.active]?.contentWindow 去拿到当前的 iframe,因为前面的contentWindow没有销毁的情况,导致切换到第二个iframe的时候,还没有滚动就显示滚动到底部。
if (contentWindow) {
contentWindow.addEventListener("scroll", (e) => {
this.handleIframeScroll(e, this.iframeElements[this.active]?.contentWindow);
});
console.log("✅ 已绑定 scroll 事件");
} else {
console.warn("⚠️ iframe contentWindow 不存在");
}
} catch (e) {
console.error("🚫 访问 iframe 出错:", e.message);
}
},
// 滚动过程
handleIframeScroll(e,contentWindow) {
const iframe = e.target; // 当前滚动的 iframe 元素
try {
if (!contentWindow) {
console.warn("⚠️ contentWindow 不存在");
return;
}
const doc = contentWindow.document;
if (!doc) {
console.warn("⚠️ document 不存在");
return;
}
const scrollTop =
doc.documentElement.scrollTop ||
doc.body.scrollTop ||
window.pageYOffset ||
0;
const clientHeight =
doc.documentElement.clientHeight ||
doc.body.clientHeight ||
contentWindow.innerHeight ||
0;
const scrollHeight =
doc.documentElement.scrollHeight ||
doc.body.scrollHeight ||
0;
if (scrollTop + clientHeight >= scrollHeight - 5) {
this.handleReachBottom();
}
} catch (e) {
this.isReachBottom = false;
return;
}
},
handleReachBottom() {
this.isReachBottom = true;
this.readOverList.push({ // 记录每一个链接都被完整阅读
index: this.active,
readOver: true,
});
},
切换不同的 iframe 时需要初始化阅读状态,并且重新绑定滚动监听
onClickTab(val) {
this.isReachBottom = false;
this.$nextTick(() => {
this.bindIframeScroll();
});
},