0. 背景
当你想对页面跳转逻辑添加参数,或者想动态替换域名,那么拦截页面a标签跳转,是个不错的选择!
网上有挺多实现,为啥我还写一遍?因为有些边界场景,是需要踩坑踩出来的!
1. 实现
// 不携带refer
const NO_REFER = {
REFERRER_POLICY: ['no-referrer'],
REL: ['noreferrer', 'noopener']
};
// 监听 click 事件,处理页面跳转相关逻辑
// 若在同一个元素上按下并松开鼠标左键,会依次触发 mousedown、mouseup、click,前一个事件执行完毕才会执行下一个事件
document.body.addEventListener('click', (e) => {
//【2】获取 a 标签,最多 3 层
const target = e.target || e.srcElement;
const parentNode = target.parentNode;
const grandParentNode = parentNode && parentNode.parentNode;
let actualANode = null; // 实际a标签
let url = ''; // 跳转链接
// 判断是否 a 标签
if (target.nodeName.toLocaleLowerCase() === 'a' && target.getAttribute('href')) {
actualANode = target;
} else if (parentNode && parentNode.nodeName.toLocaleLowerCase() === 'a' && parentNode.getAttribute('href')) {
// a 标签含子标签
actualANode = parentNode;
} else if (grandParentNode && grandParentNode.nodeName.toLocaleLowerCase() === 'a' && grandParentNode.getAttribute('href')) {
// a 标签含子子标签
actualANode = grandParentNode;
}
// 不处理
if (!actualANode){
return;
}
url = actualANode.getAttribute('href');
// 只处理 http:// 或 https:// 或 /xxx
if (/^https?:\/\//.exec(url) || /^\//.test(url)) {
// 适配如 vue router-link形式
// e.returnValue 为IE8 已废弃,目前打印为true
if ( e.defaultPrevented) {
return;
}
// 禁止跳转行为
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = true;
}
// 定制化处理链接跳转逻辑
// url = handleUrl(url);
//【3】处理完 a 标签的内容,重新触发跳转,根据原来 a 标签页 target 来判断是否需要新窗口打开
if (isNoRefer(actualANode)) {
// 不携带 refer
window.open(url, actualANode.getAttribute('target') || '_self', NO_REFER.REL.join(','));
} else if (actualANode.getAttribute('target') === '_blank') {
window.open(url);
} else {
window.location.href = url;
}
}
});
/**
* 判断是否设置了不携带 refer
* @param {object} el 元素
* @return {boolean} boolean
*/
function isNoRefer (el) {
if (!el) {
return false;
}
let referrerpolicy = false;
let rel = false;
const elReferrerpolicy = el.getAttribute('referrerpolicy');
const elRel = el.getAttribute('rel');
if (elReferrerpolicy) {
const referArr = elReferrerpolicy.split(' ');
referrerpolicy = !!(referArr.find(refer => NO_REFER.REFERRER_POLICY.includes(refer)));
}
if (elRel) {
const relArr = elRel.split(' ');
rel = !!(relArr.find(refer => NO_REFER.REL.includes(refer)));
}
return referrerpolicy || rel;
}
2. 注意点
2.1 框架内置路由组件
如vue 的router-link
, 如果未设置 target="__blank" 属性,其是局部刷新的形式;这时,内部实现是会调用 e.preventDefault()
;
如果此时代码实现,未判断 e.defaultPrevented 是否为true,则会导致页面刷新;对于使用了store状态的,可能就会丢失;
2.2 a标签原生属性
有些链接,是配置了noreferrer
属性,需要特殊处理