您的位置:

Vue中iframe的使用与跨域问题详解

一、 vueiframe跨域

在项目中,有时会需要引入外部的无关联组件或页面,此时可以通过iframe来实现。但是vue中的iframe涉及到跨域问题,如何解决呢?

一般来说,如果父页面和子页面不同源,则会出现跨域问题。所以我们可以在后台设置响应头中的Access-Control-Allow-Origin,将源地址设置为'*',即可允许所有外域请求。

后台代码示例(node.js):

``` app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); next(); }); ```

二、vueiframe只需载一次

每次切换路由或者重新加载页面时,vue会重新创建组件和iframe,这会造成很大的性能浪费。如何避免重复载入呢?

我们可以通过keep-alive来缓存组件,避免重新渲染。同时,设置一个变量checked来标记iframe是否已经载入,如果已经载入则不需要重新载入。

代码示例:
<template>
  <keep-alive>
    <div>
      <iframe 
        id="my-iframe"
        v-if="!checked"
        :src="url" 
        @load="frameOnload">
      </iframe>
    </div>
  </keep-alive>
</template>
<script>
export default {
  data: () => ({
    checked: false,
  }),
  methods: {
    frameOnload() {
      this.checked = true
    }
  }
}
</script>

三、vueiframe路径

iframe路径可以直接使用src属性指定。但是如果需要动态修改路径,可以使用Vue的响应式特性。

下面是vue中设置iframe路径的代码示例:

代码示例:
<template>
  <div>
    <iframe :src="iframeSrc"></iframe>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        src: "https://example.com"
      }
    },
    computed: {
      iframeSrc () {
        return this.src + "?time=" + (new Date()).getTime()
      }
    }
  }
</script>

四、vueiframe 传参

在vue中,可以使用props来给iframe传递参数。我们可以通过监听变化,再把变化传至iframe中。

下面是一个vue传递至iframe中的示例:

代码示例:
<template>
  <iframe 
    ref="iframe" 
    @load="loaded" 
    :src="src" 
    :width="width + 'px'"
    :height="height + 'px'"
  ></iframe>
</template>

<script>
  export default {
    props: ['src', 'width', 'height'],
    methods: {
      loaded() {
        this.$refs.iframe.contentWindow.postMessage(
          {
            type: 'init',
            data: {
              parentId: this.$route.query.parentId || ''
            }
          },
          '*'
        )
      }
    }
  }
</script>

五、vueiframe嵌套跨域

实现父子组件之间的通信,可以使用postMessage,它可以跨域传输数据。

代码示例:
父页面:

  <template>
    <div>
      <iframe :src="frameUrl"></iframe>
    </div>
  </template>
  
  <script>
  export default {
    data() {
      return {
        frameUrl: ""
      }
    },
    mounted(){
      this.$nextTick(() =>{
        const iframeWindow = this.$el.querySelector('iframe').contentWindow;
        iframeWindow.onload = () => {
          iframeWindow.postMessage({ type: 'test-message' },config.targeturl)
        }
      })
    }
  }
  </script>


子页面:

  <template>
    <div>承载页</div>
  </template>
  
  <script>
  export default {
    created(){
      window.addEventListener('message', this.messageHandler)
    },
    destroyed(){
      window.removeEventListener('message', this.messageHandler)
    },
    methods: {
      messageHandler(event) {
        if(event.source !== parent) return;
        let message = event.data;
        if(message.type === 'test-message') {
          console.log('test-message')
        }
      }
    }
  }
  </script>

六、vueiframe页面切换

子页面中的iframe通过postMessage把数据传给父页面,并进行组件切换。

代码示例:
父页面:

<template>
  <div>
    <h2>父页面</h2>
    <router-view :key="$route.fullPath"></router-view>
  </div>
</template>

<script>
export default {
  data: () => ({
    data: ""
  }),
  methods: {
    changeRoute(data) {
      this.$router.push(data.path)
      this.data = data
    }
  }
}
</script>

子页面:

<template>
  <div>
    <h2>iframe中子页面</h2>
    <button @click="changeRoute">切换</button>
  </div>
</template>

<script>
export default {
  created() {
    window.addEventListener('message', this.messageHandler)
  },
  destroyed() {
    window.removeEventListener('message', this.messageHandler)
  },
  methods: {
    changeRoute() {
      this.$router.push('/')
      window.parent.postMessage({ path: '/' }, '*')
    },
    messageHandler(event) {
      const message = event.data
      if (message.path) {
        this.$router.push(message.path)
      }
    }
  }
}
</script>

七、vueiframe只需在一次

如何使iframe只需加载一次,涉及到Vue的$once方法。

代码示例:
<template>
  <iframe v-once :src="url"></iframe>
</template>

<script>
  export default {
    data () {
      return { url: 'https://example.com' }
    }
  }
</script>

八、vueiframe加载不了外部URL

有时候vue中的iframe会因为加载的外部网址存在x-frame-options保护机制而失败。但是我们可以使用其他的方案,比如使用fetch或者axios获取网址内容,再渲染到自己的页面中。

代码示例:
<template>
  <div>
    <h2>外部网址:{{url}}</h2>
    <div v-html="html"></div>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        url: "https://example.com",
        html: ""
      }
    },
    created(){
      fetch(this.url, {mode: "no-cors"})
        .then(resp => resp.text())
        .then(html => this.html = html)
    }
  }
</script>

九、vueiframe跨域传值修改里面的样式

有时候我们需要修改iframe中的样式,可以使用postMessage进行通信。

代码示例:
父页面:

  <template>
    <div>
      <h2>父页面</h2>
      <iframe :src="frameUrl"></iframe>
    </div>
  </template>
  
  <script>
  export default {
    data() {
      return {
        frameUrl: ""
      }
    },
    mounted(){
      this.$nextTick(() =>{
        const iframeWindow = this.$el.querySelector('iframe').contentWindow;
        iframeWindow.onload = () => {
          iframeWindow.postMessage({ 
            type: 'change-style', 
            data: {
              backgroundColor: '#f5f5f5'
            }
          },config.targeturl)
        }
      })
    }
  }
  </script>


子页面:

  <template>
    <div>承载页</div>
  </template>
  
  <script>
  export default {
    created(){
      window.addEventListener('message', this.messageHandler)
    },
    destroyed(){
      window.removeEventListener('message', this.messageHandler)
    },
    methods: {
      messageHandler(event) {
        if(event.source !== parent) return;
        let message = event.data;
        if(message.type === 'change-style') {
          let ele = document.body
          for(let key in message.data){
            ele.style[key] = message.data[key]
          }
        }
      }
    }
  }
  </script>