整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JQuery实现浏览器点击后退,iframe页面空白

JQuery实现浏览器点击后退,iframe页面空白处理

frame页面在点击浏览器后退功能后,直接空白,体验不佳!

------处理方案如下------

在对应的JSP页面中直接加上:

$(document).ready(function($) {

if (window.history && window.history.pushState)

{

$(window).on('popstate', function ()

{

var hashLocation=location.hash;

var hashSplit=hashLocation.split("#!/");

var hashName=hashSplit[1];

if (hashName !=='')

{

var hash=window.location.hash;

if (hash==='')

{

//在浏览器点击后退时,固定在当前页

window.history.pushState('forward', null, '');

return;

}

}

});

window.history.pushState('forward', null, '');

}

});

2年之前碰到的处理方案,希望对大家有用。

、跨域请求痛点

最近网站新增了一个域名B用于分离不同的功能。但是需要复用服务器的高防等服务,但是服务和原有域名A绑定,所以新域名B需要直接去调用域名A。

一开始想使用CNAME的方式,让B直接指向A。但是Https支持性有点问题,需要多域名证书。也考虑过反向代理,但是代理服务器的性能和高防等又是一个问题。

最终决定在域名B的网页中,所有请求都直接去调用域名A的接口。于是就遇到了跨域请求的问题。

二、跨域请求的实现方式

网上找了许多资料来实现跨域请求。最终预估下来,有两种方案比较靠谱:通过iframe实现和CORS方案

三、通过iframe实现

初步设想是加载一个域名A的iframe页面,然后通过postMessage将所有Ajax请求,转发给这个页面,通过这个页面来进行请求,最终将结果通过postMessage回发给外层的域名B页面。

于是开始实现:

前端使用的是React,所以实现了一个FrameHttp.js专门用法封装ajax调用。调用FrameHttp.ajax将所有外部Jquery请求转发给iframe中域名A的/util/ajaxrequest页面。

import Tools from "../Tools"

const FrameHttpCmd={
    INIT: 1,
    REQUEST: 2,
    REQUEST_CALLBACK: 3,
}

class FrameHttp {
    static isInit=false
    static _request_buffer=[]
    static frame=null
    static message_key=0
    static message_key_max=10000000
    static request_map={}

    static init(domain) {
        var the_frame=document.createElement('iframe')
        let url_obj=new URL(domain)
        url_obj.pathname=Tools.getUrl('/util/ajaxrequest')
        the_frame.src=url_obj.toString()
        the_frame.style.visibility='hidden'
        the_frame.style.position='absolute'
        the_frame.style.width=0
        the_frame.style.height=0

        FrameHttp.frame=the_frame
        document.body.appendChild(the_frame)
        window.addEventListener('message', this.onMessage)
    }

    static _initFrame() {
        FrameHttp.isInit=true
        console.log('(INFO)FrameHttp._initFrame')
        for (let i=0; i < FrameHttp._request_buffer.length; i++) {
            let arg=FrameHttp._request_buffer[i]
            FrameHttp.ajax(arg)
        }
        FrameHttp._request_buffer=[]
    }

    static getMessageKey() {
        let message_key=FrameHttp.message_key
        FrameHttp.message_key=(FrameHttp.message_key+1)%FrameHttp.message_key_max 
        return message_key
    }

    static ajax(arg) {
        if (FrameHttp.isInit) {
            // console.log(arg)
            const { success, error, ...others }=arg
            let message_key=FrameHttp.getMessageKey()
            FrameHttp.request_map[message_key]={ success, error }
            FrameHttp.frame.contentWindow.postMessage({
                cmd: FrameHttpCmd.REQUEST,
                data: others,
                key: message_key,
            }, '*')
            console.log('(INFO)FrameHttp.ajax', others)
        }
        else {
            FrameHttp._request_buffer.push(arg)
            console.log('(INFO)FrameHttp.ajax:push buffer')
        }
    }

    static onMessage(e) {
        const { data }=e
        if (data.cmd==FrameHttpCmd.INIT) {
            FrameHttp._initFrame()
        }
        else if (data.cmd==FrameHttpCmd.REQUEST_CALLBACK) {
            // console.log(data.key, data.success)
            let item=FrameHttp.request_map[data.key]
            if (data.error) {
                if (item.error) {
                    item.error(data.error)
                }
            }
            else {
                if (item.success) {
                    item.success(data.success)
                }
            }
            delete FrameHttp.request_map[data.key]
        }
    }
}

export default FrameHttp
export { FrameHttpCmd }

然后域名A中的/util/ajaxrequest页面处理请求:

import React, { Component } from 'react'
import { FrameHttpCmd } from '../../../common_js/web_frame/FrameHttp'
import jquery from '../../../common_js/jquery.min'

class AjaxRequest extends Component {
    constructor(props) {
        super(props)

        this.onMessage=this.onMessage.bind(this)
    }

    onMessage(e) {
        const { cmd, data, key, cookie }=e.data
        if (cmd==FrameHttpCmd.REQUEST) {
            // console.log(key, data)
            console.log(document.cookie)
            jquery.ajax({
                ...data,
                success: (data)=>{
                    window.parent.postMessage({
                        cmd: FrameHttpCmd.REQUEST_CALLBACK,
                        key,
                        success: data,
                    }, '*')
                },
                error: ()=>{
                    window.parent.postMessage({
                        cmd: FrameHttpCmd.REQUEST_CALLBACK,
                        key,
                        error: 'error',
                    }, '*')
                },
            })
        }
    }

    componentDidMount() {
        window.parent.postMessage({
            cmd: FrameHttpCmd.INIT,
        }, '*')
        window.addEventListener('message', this.onMessage)
    }

    render() {
        return null
    }
}
                
export default AjaxRequest

如此实现后,发现iframe因为跨域问题无法加载

因为后端是由Django实现的,经过查阅发现,在views中需要进行处理,添加xframe_options_exempt装饰器,实现跨域加载iframe。

@xframe_options_exempt
def indexCross(request, *args, **kwargs):
    return render(request, 'index.html', {})

功能能够正常请求,但是请求中cookie并没有正常传送。

经过排查发现,对于跨域的iframe,google浏览器默认对于cookie中SameSite这个参数是LAX,会导致只有同源才能设置服务器返回的Set-Cookie。所以需要将服务器返回的Set-Cookie中指定SameSite为None,这样前端才能成功设置Cookie。

于是服务端Django引入了中间件django-cookies-samesite。settings.py进行如下修改:

INSTALLED_APPS=[
    ...
    'corsheaders',
    ...
]

# 中间件request按照注册顺序顺序执行,response按照注册顺序倒序执行
MIDDLEWARE=[
    'django_cookies_samesite.middleware.CookiesSameSite', # 此处response需要最后执行
    ...
]

SESSION_COOKIE_SECURE=True
SESSION_COOKIE_SAMESITE='None'

至此实现了iframe方式跨域。

四、CORS方案

这个需要前端Jquery加入两个参数:

jquery.ajax({
    ...
    xhrFields: {
        withCredentials: true
    },
    crossDomain: true,
})

然后服务端需要开启跨域请求支持。Django下引入中间件django-cors-headers。settings.py下如下设置:

MIDDLEWARE=[
    ...
    'corsheaders.middleware.CorsMiddleware', # 这个位置越高越好,至少要高于CommonMiddleware
    ...
]

# 跨域配置
CORS_ALLOW_CREDENTIALS=True
# CORS_ORIGIN_ALLOW_ALL=True
CORS_ORIGIN_WHITELIST=[
    'https://www.xxx.com',# 域名B
]
CORS_ALLOW_METHODS=[
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
]
CORS_ALLOW_HEADERS=[
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
    'cache-control',
]

此方案也需要和iframe一样,解决cookie中SameSite的问题。

至此CORS方案跨域也实现了。

最近在项目中遇到了一个需要动态添加 select option 选项的问题,大概功能就是iframe不同子页面之间,将iframe3选中的checkbox内容作为iframe4中一个select下拉框的选项。上网查阅资料后发现有js实现的,也有jquery实现的,根据自己的要求我"添加"采用js方式实现,"删除"采用jquery方式实现(js也可以实现删除,是根据选中内容的索引下标实现的),根据自己的实际需求我选择了jquery,其中有个方法是根据value值删除的。

1.js动态添加option

2.jquery 动态删除option

根据value值删除option

3.js动态删除option

根据下标索引删除option

4.js删除所有option

删除所有option

上面提及到了iframe,实际项目中获取select元素id不是这么简单,这里是简化了只是为了说明添加和删除option。下面总结下常见的几种iframe子页面与子页面以及子页面与父页面之间如何取值,希望可以帮助跟我之前一样取不到值而困惑的朋友哈。这里假设有irame1和iframe2子页面。

1.父页面获取子页面的值

2.子页面获取父页面的值,并赋值(子页面操作父页面)

3.两个子页面相互操作(iframe1操作iframe2)

以上都是js获取的方法,还有jquery的方式,方法不一样,基础原理都是一样的,父页面可以拿到子页面的值,子页面之间不能之间取值,子页面要先通过父页面元素拿到其他子页面iframe Id,再去访问其他子页面的元素。

jquery不同ifame之间取值

哪里写的不妥的地方,欢迎大家在评论提出你的意见或建议哈。