@EncyKe
2018-02-23T14:18:17.000000Z
字数 9231
阅读 4162
#手记
一般而言,对于一套有用户登录需求的网站,从安全性、拓展性、低耦合等方面来考虑,主要展示页面和用户登录等个人信息相关页面会被设计成两套不同的站点,比如:www.domain.com/
以及 user.domain.com/
;甚至协议也会不同了,比如:http://www.domain.com/
以及 https://user.domain.com/
。
这类似于前台和后台的概念:前台对于未登录和已登录的用户均开放,可能表现形式稍有不同;后台则必须以用户登录作为前置条件方可开放,展示一些个人信息、设置、消息等。下文将以前台和后台分别称呼这两个站点,以探求两者通信的解决方案。
iframe
框架结构未登录用户从前台使用登录/注册功能时,此时若使之跳转至一个单独的后台登录/注册页面,并让用户在此页面上完成登录/注册流程时,未免使得用户体验变差,并且成功后的跳转可能也难以实现。
理想的方式可以是:点击前台的「登录/注册」按钮 => 弹出或下拉登录/注册弹窗(以及遮罩层) => 完成账号密码输入并提交 => 去往指定页面(一般刷新当前页面即可,由何处登录即返回原处)。
而要把一个单独的后台登录页面变成前台页面的假弹窗,使用 iframe
嵌入无疑会是一个比较好的方法。
分析前台页面和后台页面的角色和功能,可以得出如下需求——
iframe
弹窗;从插件的易用性和扩展性角度讲,把遮罩层整合到后台页面中是比较合理的做法。事实上,前台页面要做的仅仅只是给相应的「登录」按钮绑定打开 iframe
的事件,剩下的一切操作逻辑都交由前台页面来完成。
iframe
的同源通信若前台页面中登录的应用场景并不太多,那么用 JS 拼接输出 HTML 片段是可以接受的——
// === jQuery ===
var frameURL = './back.html';
$('button.js-login')
.off('click')
.on('click', function() {
$(document.body).prepend(
'<iframe class="js-frame" src="' + frameURL + '"></iframe>'
);
});
iframe
父子通信操作当前后台两个站点同源时,可以直接用 JS 或 jQuery 互相操作页面元素通信,达到我们想要的效果。在本例中,前台页面为父页面,后台页面为子页面。
父页面获取子页面 document 对象
// === JS ===
document.getElementsByTagName('iframe')[0].contentWindow.document;
window.frames[0].document;
// === jQuery ===
$('iframe').contents();
子页面获取父页面 document 对象
// === JS ===
window.parent.document;
// === jQuery ===
$('<selector>', parent.document);
我们让前台页面仅触发 iframe
,而其它操作均由后台页面来处理,主要包括单击关闭按钮和遮罩层关闭 iframe
,提交表单后刷新前台页面,关键代码如下——
// === jQuery ===
$('.js-cover, .js-close')
.off('click')
.on('click', function() {
$('.js-frame', window.parent.document).remove();
});
$('.js-submit')
.off('click')
.on('click', function() {
window.parent.location.reload();
});
window.parent.location.reload();
应该是在数据提交成功后的回调函数中进行,例子中的事件只作演示用。iframe
跨域通信跨域通信时,可以用 HTML5 的新特性 window.postMessage
来发送、接收数据,实现通信。
我们不妨指定前台页面的域名为 http://www.domain.com
,后台页面域名为 https://user.domain.com
。
window.postMessage
这个新 API 的用法非常简单——
objectWindow.postMessage(message, domain);
objectWindow
是指当前页面要发送消息到达的目标页面,可以为如下对象—— var objectWindow = window.frames[0].document;
var objectWindow = window.parent;
var objectWindow = window.open();
var objectWindow = window.opener();
message
是当前页面要发送的信息,通常为字符串,由目标页面接收后作判断、处理;domain
是目标页面的域名;需注意是域名,形如 http://www.domain.com
而非 http://www.domain.com/
;可以设为 '*'
,但不建议按此设置;
// === jQuery ===
var frontDomain = 'http://www.domain.com';
$('.js-cover, .js-close')
.off('click')
.on('click', function() {
window.parent.postMessage('close', frontDomain);
});
$('.js-submit')
.off('click')
.on('click', function() {
window.parent.postMessage('reload', frontDomain);
});
接收端通过监听 onmessage
事件即可实现接收。可接收信息内容 (event.data
) 以及消息来源地址 (event. origin
) 等。
// === jQuery ===
var onmessage = function (event) {
var backDomain = 'https://user.domain.com';
var data = event.data;
var origin = event.origin || event.originalEvent.origin;
if (origin !== backDomain) {
return;
} else {
if (data === 'reload') {
location.reload();
} else if (data === 'close') {
$('.js-frame').remove();
};
};
};
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent !== 'undefined') {
window.attachEvent('onmessage', onmessage);
};
通过上述代码可见,domain
和 event.origin
是消息收发两端互相确认身份的重要设置。为了提高可扩展性,我们可以把前台的域名挂在弹窗的 URL 里 var frameURL = 'http://www.domain.com/?callback_url=http://www.domain.com';
。在后台页面里截取这个 callback_url
作为发送消息的 domain
——
var href = location.href;
var arg = 'callback_url=http';
if (!!href.match(arg)) {
var domain = 'http' + href.split(arg)[1].split('&')[0];
$('.js-cover, .js-close')
.off('click')
.on('click', function() {
window.parent.postMessage('close', domain);
});
$('.js-submit')
.off('click')
.on('click', function() {
window.parent.postMessage('reload', domain);
});
};
在跨域调用的 API 需要读取 cookie 时,此时是无法奏效的。解决方法是设置 new XMLHttpRequest().withCredentials = true
同步传入 cookie,或者jQuery 设置 xhrFields: { withCredentials: true }
。具体代码如下——
// === jQuery ===
$.ajax({
url: '/path/to/file',
type: 'default GET (Other values: POST)',
xhrFields: {
withCredentials: true
},
dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
data: {param1: 'value1'},
})
.done(function() {
console.log("success");
})
.fail(function() {
console.log("error");
})
.always(function() {
console.log("complete");
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Front</title>
<style type="text/css">
body {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
nav {
height: 50px;
line-height: 50px;
background: rgba(0, 0, 255, .4);
text-align: center;
}
iframe {
position: fixed;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 0;
overflow: hidden;
}
</style>
</head>
<body>
<nav>
<button class="js-login">登录</button>
</nav>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var frameURL = './back.html';
$('button.js-login')
.off('click')
.on('click', function() {
$(document.body).prepend(
'<iframe class="js-frame" src="' + frameURL + '"></iframe>'
);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Back</title>
<style type="text/css">
form {
position: absolute;
z-index: 20;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px 30px;
box-shadow: 0 0 10px rgba(0, 0, 0, .2);
border-radius: 5px;
}
input {
display: block;
box-sizing: border-box;
margin: 15px auto;
padding: 0 10px;
width: 200px;
line-height: 30px;
border-radius: 5px;
border: 1px solid rgba(0, 0, 0, .2);
}
.cover {
position: fixed;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(255, 255, 255, .8);
z-index: 10;
}
.close {
position: absolute;
right: 0;
top: 0;
padding: 5px;
color: rgba(0, 0, 0, .6);
cursor: pointer;
}
</style>
</head>
<body>
<div class="cover js-cover"></div>
<form>
<span class="close js-close">x</span>
<input type="text" name="user" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<input type="button" name="submit" value="登录" class="js-submit" />
</form>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
$('.js-cover, .js-close')
.off('click')
.on('click', function() {
$('.js-frame', window.parent.document).remove();
});
$('.js-submit')
.off('click')
.on('click', function() {
window.parent.location.reload();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Front</title>
<style type="text/css">
body {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
nav {
height: 50px;
line-height: 50px;
background: rgba(0, 0, 255, .4);
text-align: center;
}
iframe {
position: fixed;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 0;
overflow: hidden;
}
</style>
</head>
<body>
<nav>
<button class="js-login">登录</button>
</nav>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var frameURL = 'http://www.domain.com/?callback_url=http://www.domain.com';
var backDomain = 'https://user.domain.com';
$('button.js-login')
.off('click')
.on('click', function() {
$(document.body).prepend(
'<iframe class="js-frame" src="' + frameURL + '"></iframe>'
);
});
var onmessage = function (event) {
var data = event.data;
var origin = event.origin || event.originalEvent.origin;
if (origin !== backDomain) {
return;
} else {
if (data === 'reload') {
location.reload();
} else if (data === 'close') {
$('.js-frame').remove();
};
};
};
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent !== 'undefined') {
window.attachEvent('onmessage', onmessage);
};
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Back</title>
<style type="text/css">
form {
position: absolute;
z-index: 20;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px 30px;
box-shadow: 0 0 10px rgba(0, 0, 0, .2);
border-radius: 5px;
}
input {
display: block;
box-sizing: border-box;
margin: 15px auto;
padding: 0 10px;
width: 200px;
line-height: 30px;
border-radius: 5px;
border: 1px solid rgba(0, 0, 0, .2);
}
.cover {
position: fixed;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(255, 255, 255, .8);
z-index: 10;
}
.close {
position: absolute;
right: 0;
top: 0;
padding: 5px;
color: rgba(0, 0, 0, .6);
cursor: pointer;
}
</style>
</head>
<body>
<div class="cover js-cover"></div>
<form>
<span class="close js-close">x</span>
<input type="text" name="user" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<input type="button" name="submit" value="登录" class="js-submit" />
</form>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var href = location.href;
if (!!href.match(arg)) {
var arg = 'callback_url=http';
var domain = 'http' + href.split(arg)[1].split('&')[0];
$('.js-cover, .js-close')
.off('click')
.on('click', function() {
window.parent.postMessage('close', domain);
});
$('.js-submit')
.off('click')
.on('click', function() {
window.parent.postMessage('reload', domain);
});
};
</script>
</body>
</html>