vue2监听 iframe 内部滚动实现

4 阅读1分钟

大家好,我是瑶琴呀!

开门见山,这次需要实现的功能: 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();
      });
    },