iframe在顶级页面弹窗并交互

思路如下:

使用window.top添加元素到顶层页面,
通过Portal将子组件渲染到该元素,
在iframe内部可以写弹窗内的布局,
在window.top添加样式和方法,
在找到对应的元素设置监听onclick/oninput方法,
然后就可以操作mobx数据,
使顶级页面和iframe进行交互。

相关代码如下:

  • iframeUtil.js

/**
 * 在文档 iframeDocument 中添加样式
 * @param {*} iframeDocument
 * @param {*} cssText
 */
const addCSS = (iframeDocument, cssText) => {
    var style = iframeDocument.createElement('style'), //创建一个style元素
        head = iframeDocument.head || iframeDocument.getElementsByTagName('head')[0]; //获取head元素
    style.type = 'text/css'; //这里必须显示设置style元素的type属性为text/css,否则在ie中不起作用
    if (style.styleSheet) {
        //IE
        var func = function() {
            try {
                //防止IE中stylesheet数量超过限制而发生错误
                style.styleSheet.cssText = cssText;
            } catch (e) {}
        };
        //如果当前styleSheet还不能用,则放到异步中则行
        if (style.styleSheet.disabled) {
            setTimeout(func, 10);
        } else {
            func();
        }
    } else {
        //w3c
        //w3c浏览器中只要创建文本节点插入到style元素中就行了
        var textNode = iframeDocument.createTextNode(cssText);
        style.appendChild(textNode);
    }
    head.appendChild(style); //把创建的style元素插入到head中
};

const addTopModal = () => {
    const topModal = document.createElement('div');
    topModal.setAttribute('id', 'topModal');
    topModal.setAttribute(
        'style',
        'position:absolute;z-index:999;width:100%;height:100%;background:rgba(0,0,0,0.5);overflow:hidden;top: 0;bottom: 0;left: 0;right: 0;'
    );
    window.top.document.body.appendChild(topModal);
};

/**
 * 添加全局的 topModal
 * 如果已存在,那么不做处理,
 * 如果不存在,那么添加。
 * @returns
 */
const addTopModalAndCheckExist = () => {
    let topModal = window.top.document.getElementById('topModal');
    if (!topModal) {
        addTopModal();
        topModal = window.top.document.getElementById('topModal');
    }
    window.top.topModal = topModal;
    return topModal;
};

/**
 * 移除全局的 topModal
 * @returns
 */
const removeTopModal = () => {
    if (window.top.topModal) {
        if (window.top.topModal.parentElement) {
            window.top.topModal.parentElement.removeChild(window.top.topModal);
        }
        window.top.topModal = null;
    }
};

/**
 * 初始化全局的 topModal
 * IframeServicesCatalog 相关
 * 每一个弹窗要维护自己的 init 方法
 * 内嵌iframe只需要提供关闭弹窗功能
 * 其他的内容都是可以直接展示出来的
 *
 * @returns
 */
const initTopModalIframeServicesCatalog = () => {
    const eleClose = window.top.document.getElementById('iframeServicesCatalog-close');
    eleClose.onclick = window.top.customTopModal.onClickClose;
};

/**
 * 初始化全局的 topModal
 * TopModalForm 相关
 * 每一个弹窗要维护自己的 init 方法
 * 在顶级页面弹窗展示form表单,
 * 并设置相关监听,
 * 同步数据到mobx,
 * TopModalForm 示例添加;
 *
 *
 * @returns
 */
const initTopModalForm = () => {
    const eleClose = window.top.document.getElementById('TopModal-close');
    eleClose.onclick = window.top.customTopModal.onClickClose;
    const btnUpdateMobxData = window.top.document.getElementById('customTopModal.btnUpdateMobxData');
    btnUpdateMobxData.onclick = window.top.customTopModal.onClickUpdateMobxData;
    const inputUpdateMobxData = window.top.document.getElementById('customTopModal.inputUpdateMobxData');
    // inputUpdateMobxData.onchange = window.top.customTopModal.onChangeInput; // 这个无效 input监听需要使用oninput
    inputUpdateMobxData.oninput = window.top.customTopModal.onChangeInput;
};

const customIframe = { addCSS };
const customTopModal = {
    initTopModalIframeServicesCatalog,
    initTopModalForm,
    addTopModalAndCheckExist,
    removeTopModal
};

//全局调用 iframe 相关方法
window.top.customIframe = customIframe;

//全局调用 topModal 相关方法
window.top.customTopModal = customTopModal;

export { customIframe, customTopModal };
  • Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
    components/topModal.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { customTopModal } from '@/utils/iframeUtil';

class TopModal extends React.Component {
    constructor(props) {
        super(props);
        customTopModal.addTopModalAndCheckExist();
        this.el = document.createElement('div');
    }

    componentDidMount() {
        // 在 Modal 的所有子元素被挂载后,
        // 这个 portal 元素会被嵌入到 DOM 树中,
        // 这意味着子元素将被挂载到一个分离的 DOM 节点中。
        // 如果要求子组件在挂载时可以立刻接入 DOM 树,
        // 例如衡量一个 DOM 节点,
        // 或者在后代节点中使用 ‘autoFocus’,
        // 则需添加 state 到 Modal 中,
        // 仅当 Modal 被插入 DOM 树中才能渲染子元素。
        window.top.topModal.appendChild(this.el);
    }

    componentWillUnmount() {
        customTopModal.removeTopModal();
    }

    render() {
        return ReactDOM.createPortal(this.props.children, this.el);
    }
}

export default TopModal;


  • stores/TopModalForm.js mobx 数据管理
import { observable, action, runInAction } from 'mobx';
// import { querySrvcatsWithoutChild } from '@/services/api';

export default class TopModalForm {
    @observable
    data = [];
    @observable
    inputValue = '';

    @action.bound
    getData = async () => {
        // const res = await querySrvcatsWithoutChild();

        const menuNames = ['a', 'b', 'c', 'd'];

        runInAction(() => {
            this.data = menuNames;
        });
    };
    @action.bound
    onChangeInput = async value => {
        this.inputValue = value;
    };
    @action.bound
    onClickUpdateMobxData = async () => {
        this.inputValue = `value${this.inputValue}`;
    };
}

  • routes/TopModalForm/customStyle.js 要添加到window.top 的样式

const borderStyle = '1px solid #e6eaef';

let topModal = `
#TopModalForm {
  margin: 0 auto;

}

.TopModalForm-wrap {
  width: 70%;
  margin: 80px auto;
  height: 640px;
  background: #12335B;
}
.TopModalForm-header {
  border-bottom: ${borderStyle};
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 20px;
  padding: 10px;
}


`;
// let isPreviewPage = ".isPreviewPage {background: red;}"

let styleText = topModal;
// +isPreviewPage
export default styleText;

  • routes/TopModalForm/TopModalForm.js
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import { Icon, Input, Button } from '@xxx/components';
import TopModal from '@/components/topModal';
import { withRouter } from 'react-router';
import gd_icon from '../../../public/static/images/gd_icon.png';
import { customTopModal } from '@/utils/iframeUtil';

@withRouter
@inject('topModalFormStore')
@observer
class TopModalForm extends Component {
    constructor(props) {
        super(props);
        // todo 需要在构造器里面初始化相关方法才能在当前页面正常使用
        window.top.customTopModal.onChangeInput = this.onChangeInput;
        window.top.customTopModal.onClickUpdateMobxData = this.onClickUpdateMobxData;
        this.state = {};
    }

    onClickUpdateMobxData = e => {
        console.log('onClickUpdateMobxData.e', e);
        const { inputValue } = this.props.topModalFormStore;
        console.log('onClickUpdateMobxData.inputValue', inputValue);

        const { onClickUpdateMobxData } = this.props.topModalFormStore;
        onClickUpdateMobxData();
    };
    onChangeInput = e => {
        console.log('onChangeInput.e', e);
        const value = e.target.value;
        const { onChangeInput } = this.props.topModalFormStore;
        onChangeInput(value);
        const { inputValue } = this.props.topModalFormStore;
        console.log('onChangeInput.inputValue', inputValue);
    };

    initData = async () => {
        customTopModal.initTopModalForm();
    };

    componentDidMount() {
        this.initData();
    }

    componentWillUnmount() {}

    render() {
        const { title, data: dataProps } = this.props;
        const { data, inputValue } = this.props.topModalFormStore;

        return (
            
{title}
本地图片使用示例: 暂无图片
props数据使用示例: {dataProps.map((item, i) => { return item.name; })}
mobx数据使用示例: {data.map((item, i) => { return item; })}
mobx数据使用示例: 输入框内容为{inputValue}
); } } export default TopModalForm;
  • routes/TopModalForm/TopModalFormEntry.js
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import { withRouter } from 'react-router';
import styleText from './customStyle';
import TopModalForm from './TopModalForm';
import { customIframe, customTopModal } from '@/utils/iframeUtil';

@withRouter
@inject('topModalFormStore')
@observer
class TopModalFormEntry extends Component {
    constructor(props) {
        super(props);
        this.state = {
            visibleTopModal: false,
            title: 'title'
        };
    }

    initData = async () => {
        this.props.topModalFormStore.getData();
        customIframe.addCSS(window.top.document, styleText);
        window.top.customTopModal.onClickClose = this.onClickClose;
    };

    componentDidMount() {
        this.initData();
    }

    componentWillUnmount() {}

    onClickClose = () => {
        console.log('onClick 关闭');
        this.setState({
            visibleTopModal: false
        });
    };

    showTopModal = () => {
        console.log('onClick 展示');
        this.setState({
            visibleTopModal: true
        });
    };

    render() {
        const { title, visibleTopModal } = this.state;
        const { data } = this.props.topModalFormStore;

        return (
            
{ this.showTopModal(); }} > 子级页面 TopModalForm {data.map((item, i) => { return item; })}
{visibleTopModal && }
); } } export default TopModalFormEntry;

参考链接:

  • Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
    https://react.docschina.org/docs/portals.html

你可能感兴趣的:(iframe在顶级页面弹窗并交互)