首页 前端知识 web前端之标签页相互通信、浏览器窗口的open方法的参数及防多开功能、本地存储之local与session和storage监听、动态自定义元素属性及选择器、伪类hover与active、样式混合器

web前端之标签页相互通信、浏览器窗口的open方法的参数及防多开功能、本地存储之local与session和storage监听、动态自定义元素属性及选择器、伪类hover与active、样式混合器

2024-07-29 00:07:21 前端知识 前端哥 771 364 我要收藏

MENU

  • 文章技能
  • 效果图
  • 公共代码和解析
    • style-scss
    • style-css
    • html
    • window.open的解析
    • localStorage和sessionStorage的监听区别和注意事项(坑)
  • openKeyOnly方式(壹)
  • BroadcastChannel方式(贰)
  • localStorage方式(叁)
  • 三种方式的区别
  • 彩蛋


文章技能

1、了解css混合器(mixin);
2、了解动态自定义元素(标签)属性,el.setAttribute('data-activa', '');setAttribute方法需要传递两个参数,第二个参数如果不传会报错;可以传空字符串,此时标签只显示属性,等号和属性值不会显示,标签结果<span data-activa></span>;传递的参数值都会转成字符串,比如nullundefined都会转成'null''undefined',标签结果<div data-activa="null"></div><p data-activa="undefined"></p>
3、了解自定义元素(标签)属性的选择器,.item[data-activa]|.item[data-activa='highlight ']
4、了解window.open第二个参数的作用,以及固有值和自定义值对页面交互效果的区别;
5、了解标签页之间传参的区别;
6、了解storage监听中localStorage和sessionStorage的区别;
7、了解浏览器防多开功能的新思路或方法;
8、了解hover和active伪类;
9、最终效果类似网页版的酷狗,第一次播放打开新的标签页,之后的播放只发送音乐数据到播放页,此方法适用于防多开的功能。


效果图

发送页


接收页


结合


公共代码和解析

style-scss

@mixin bsbb() {
    box-sizing: border-box;
}

html,
body {
    margin: 0px;
    padding: 0px;
    @include bsbb();
}

body {
    padding: 68px;
    @include bsbb();
}

// 发送页
.box {
    >.item {
        cursor: pointer;
        list-style-type: none;
        padding: 8px 0px;
        @include bsbb();
        text-align: center;
        font-size: 28px;
        font-weight: bold;
        background-color: #eeeeee;
        border-radius: 4px;
    }

    >.item:not(:first-child) {
        margin-top: 18px;
    }

    >.item:hover {
        background-color: #f0f8ff;
    }

    >.item:active {
        background-color: #00ffff;
    }

    >.item[data-activa] {
        color: #00ff00;
        background-color: #409eff;
    }
}

// 接收页
#idEl {
    height: calc(100vh - 136px);
    line-height: calc(100vh - 136px);
    margin: 0px;
    padding: 0px;
    text-align: center;
    font-size: 68px;
    font-weight: bold;
}

使用Scss(一种CSS预处理器)编写一些样式和一个名为bsbbmixin(混合器)。


1、@mixin bsbb()定义一个名为bsbb的mixin,它设置box-sizing属性为border-boxbox-sizing: border-box;确保元素的宽度和高度包括内容、内边距和边框,而不是仅仅包括内容。
2、html, body选择器设置htmlbody元素的marginpadding0px,并调用bsbb混合器。
3、body选择器设置body元素的padding68px,并再次调用bsbb混合器。
4、.box选择器定义.box类的样式,它包含一个.item类的子元素。.box > .item选择器设置.item的样式,包括鼠标指针样式、无列表项标记、内边距、box-sizing、文本居中、字体大小、字体粗细、背景颜色和边框圆角。
5、.box > .item:not(:first-child)选择器设置除了第一个.item之外的所有.itemmargin-top18px
6、.box > .item:hover选择器定义鼠标悬停在.item上时的背景颜色。
7、.box > .item:active选择器定义当.item被激活(例如点击)时的背景颜色。
8、.box > .item[data-activa]选择器定义当.item元素具有data-activa属性时的文本颜色和背景颜色。
9、#idEl选择器定义具有ididEl的元素样式,它设置高度和行高为视口高度减去136pxmarginpadding0px,文本居中,字体大小为68px,字体粗细为粗体。


.box类定义一个包含多个.item的容器,每个.item可以有不同的交互状态和样式。#idEl定义一个特定的元素,其样式可能用于页面的特定部分,如发送页和接收页。
在实际使用中,这段代码需要被编译成普通的css文件,以便在网页中使用。Scss编译器会处理这些mixin和选择器,生成相应的CSS规则。


style-css

html,
body {
    margin: 0px;
    padding: 0px;
    box-sizing: border-box;
}

body {
    padding: 68px;
    box-sizing: border-box;
}

.box>.item {
    cursor: pointer;
    list-style-type: none;
    padding: 8px 0px;
    box-sizing: border-box;
    text-align: center;
    font-size: 28px;
    font-weight: bold;
    background-color: #eeeeee;
    border-radius: 4px;
}

.box>.item:not(:first-child) {
    margin-top: 18px;
}

.box>.item:hover {
    background-color: #f0f8ff;
}

.box>.item:active {
    background-color: #00ffff;
}

.box>.item[data-activa] {
    color: #00ff00;
    background-color: #409eff;
}

#idEl {
    height: calc(100vh - 136px);
    line-height: calc(100vh - 136px);
    margin: 0px;
    padding: 0px;
    text-align: center;
    font-size: 68px;
    font-weight: bold;
}

html

发送页(send)

<ul class="box">
    <li class="item" onclick="handle(1)">1</li>
    <li class="item" onclick="handle(2)">2</li>
    <li class="item" onclick="handle(3)">3</li>
    <li class="item" onclick="handle(4)">4</li>
    <li class="item" onclick="handle(5)">5</li>
</ul>

接收页(receive)

<div id="idEl"></div>

window.open的解析

window.open方法的第二个参数用于指定新窗口的名称,名称可以用来引用新窗口,例如在后续的代码中使用window.openwindow.close方法来操作这个窗口。
第二个参数如果是_blank,则表示新窗口或标签页将在浏览器中打开,但不会与任何已存在的窗口或标签页关联。这通常用于打开一个全新的、独立的窗口或标签页。
第二个参数如果是_self,则表示新页面将在当前窗口或标签页中加载,这将替换当前页面的内容。
第二个参数如果是_parent,则表示新页面将在当前窗口或标签页的父窗口或标签页中加载。如果当前窗口或标签页没有父窗口或标签页(即它不是嵌套的),则效果与_self相同。
第二个参数如果是_top,则表示新页面将在最顶层的窗口中加载,这将替换所有嵌套的窗口或标签页。
第二个参数如果是自定义(keyOnly),且不是一个标准的窗口名称,它不会影响新窗口的行为。如果自定义(keyOnly)不是已存在的窗口名称,那么window.open会创建一个新窗口,并将其命名为keyOnly。如果keyOnly已经是一个打开的窗口的名称,那么window.open将不会创建新窗口,而是返回对已存在的窗口的引用。
在实际应用中,使用非标准的窗口名称(如keyOnly)可能不会达到预期的效果,因为浏览器可能不会识别这样的名称。通常建议使用_blank_self_parent_top作为第二个参数,以确保与浏览器的兼容性。


localStorage和sessionStorage的监听区别和注意事项(坑)

localStorage和sessionStorage的区别

localStorage和sessionStorage都是Web Storage API提供的两种存储机制,用于在客户端存储数据。它们在存储方式、生命周期、作用域等方面有所不同。
1、生命周期
localStorage,数据没有过期时间,除非被显式删除,否则数据会一直保存在浏览器中,即使关闭浏览器或重启计算机。
sessionStorage,数据仅在当前浏览器会话中有效,一旦会话结束(例如,关闭浏览器标签页或窗口),数据就会被清除。
2、作用域
localStoragesessionStorage都基于源(origin),这意味着它们只能访问相同协议、域名和端口的数据。不同源之间的数据存在隔离(跨域)。localStorage的数据在所有同源的窗口和标签页中共享,而sessionStorage的数据仅限于创建它的窗口或标签页。
3、存储大小
localStoragesessionStorage的存储大小限制因浏览器而异,但通常localStorage的存储空间比sessionStorage大。在大多数现代浏览器中,localStorage的大小限制大约为5MB到10MB。
4、API接口
两者都提供了相同的API方法,包括setItem(key, value)getItem(key)removeItem(key)clear()key(index)
5、使用场景
localStorage适合存储长期数据,如用户偏好设置、登录状态等。
sessionStorage适合存储临时数据,如表单输入、购物车内容等,这些数据仅在当前会话中需要。
6、数据持久性
localStorage提供持久化存储,即使关闭浏览器或重启计算机,数据仍然存在。
sessionStorage提供临时存储,数据仅在当前会话期间有效。
7、事件监听
两者都支持storage事件,当数据发生变化时,所有同源的窗口都会接收到这个事件。但sessionStorage事件只在当前会话的窗口中触发。在使用时,根据具体需求选择合适的存储机制。如果需要存储用户登录状态,localStorage是一个很好的选择,因为它可以跨会话持久化数据。如果需要存储用户在单个页面会话中的临时数据,sessionStorage可能更适合,因为它不会在会话结束后保留数据。


解决storage监听不被触发的问题

如果在两个页面中,只有使用localStoragestorage事件才会被触发,而使用sessionStorage时不会,这可能是因为storage事件的触发机制和sessionStorage的特性导致。


storage事件被触发的条件
1、当localStoragesessionStorage中的任何数据被修改时(例如,通过setItemremoveItemclear方法)。
2、事件会在所有同源的页面中触发,无论这些页面是打开在同一个标签页还是不同的标签页。


sessionStorage的特性
1、sessionStorage与特定的浏览器标签页或窗口关联。这意味着,如果在标签页A中修改sessionStorage,只有标签页A会接收到storage事件。其他标签页(即使它们是同一个源)不会接收到这个事件,因为它们的sessionStorage独立。
2、当标签页关闭时,与该标签页关联的sessionStorage会被清除。
据上,如果在两个不同的标签页中分别修改sessionStorage,每个标签页只会接收到自己修改的事件,而不会接收到另一个标签页的事件。这就是为什么在两个页面中,只有使用localStoragestorage事件才会被触发的原因。
如果希望在两个标签页中都能监听到sessionStorage的变化,需要确保两个标签页都注册storage事件监听器,并且它们都属于同一个源。这样,无论哪个标签页修改sessionStorage,另一个标签页都能接收到storage事件。


sessionStorage的事件监听只在修改它的标签页中触发,而localStorage的事件监听则在所有同源的标签页中触发。如果需要跨标签页监听sessionStorage的变化,需要确保所有相关标签页都注册storage事件监听器,并且它们都属于同一个源。


openKeyOnly方式(壹)

发送页(send)

function handle(val) {
    window.open(`./receive.html?val=${val}&type=keyOnly`, 'keyOnly');
}

接收页(receive)

const search = window.location.search;
const searchParams = new URLSearchParams(search);
const iterator = searchParams.keys();
const obj = {};

for (let item of iterator) obj[item] = searchParams.get(item);

idEl.textContent = obj.val;

解析
代码用于处理一个网页的URL查询参数,并将特定的参数值显示在页面上。
1、function handle(val)定义一个名为handle的函数,它接受一个参数val。
2、window.open('./receive.html?val=${val}&type=keyOnly', 'keyOnly');调用window.open方法打开一个新的浏览器窗口或标签页。它将打开./receive.html页面,并通过URL查询字符串传递两个参数val和type。val参数的值通过函数参数val赋值,type参数的值被硬编码为keyOnly。'keyOnly’是新窗口的名称。
3、const search = window.location.search;获取当前页面URL查询字符串部分,并将其赋值给变量search。如果当前URL是http://example.com/page.html?param1=value1&param2=value2,那么search的值将是?param1=value1&param2=value2
4、const searchParams = new URLSearchParams(search);使用URLSearchParams构造函数创建一个新的URLSearchParams对象,该对象封装search字符串中的查询参数。URLSearchParams对象提供一种方便的方式来处理URL的查询字符串。
5、const iterator = searchParams.keys();调用searchParams对象的keys()方法,该方法返回一个迭代器,它包含查询字符串中的所有键(参数名)。
6、const obj = {};创建一个空对象obj,用于存储查询参数的键值对。
7、for (let item of iterator) obj[item] = searchParams.get(item);使用for...of循环来遍历iterator中的每个键(参数名)。对于每个键,它使用searchParams.get(item)方法获取对应的值,并将这个键值对存储在obj对象中。
8、idEl.textContent = obj.val;将obj对象中val键对应的值赋给idEl元素的textContent属性。idEl是一个页面上的DOM元素,其ID为idEl。假设val参数存在于URL查询字符串中,并且idEl元素存在。
总结来说,以上代码的目的是从当前页面的URL查询字符串中提取val参数的值,并将其显示在页面上。如果val参数不存在,idEl.textContent将不会被设置。此外,这段代码还定义一个handle函数,用于打开一个新页面并传递参数。


BroadcastChannel方式(贰)

叙言

以下代码展示两个页面之间的通信机制,一个发送消息页面,一个接收消息页面。使用BroadcastChannelAPI来实现跨页面通信,以及localStorage来存储和传递消息计数。


发送页(send)

const channel = new BroadcastChannel('keyOnly');

function handle(val) {
    const n = +localStorage.getItem('keyOnly');

    if (!isNaN(n) && n > 0) {
        channel.postMessage({ val, type: 'keyOnly' });
    } else {
        window.open(`./receive.html?val=${val}&type=keyOnly`, '_blank');
    }
}

1、const channel = new BroadcastChannel('keyOnly');创建一个新的BroadcastChannel对象,用于在同源的不同页面之间发送和接收消息。频道名称为keyOnly
2、function handle(val) { ... }定义一个handle函数,它接收一个参数val,这个参数是需要发送的消息内容。
3、const n = +localStorage.getItem('keyOnly');localStorage中获取名为keyOnly的项,并尝试将其转换为数字。+操作符用于尝试将字符串转换为数字。
4、if (!isNaN(n) && n > 0) { ... } else { ... }如果localStorage中的值存在,且为正数,则执行channel.postMessage发送消息;否则,打开一个新的浏览器窗口或标签页,并加载./receive.html页面。
5、channel.postMessage({ val, type: 'keyOnly' });如果条件满足,使用BroadcastChannel发送一个包含val和type属性的对象。


接收页(receive)

const search = window.location.search;
const searchParams = new URLSearchParams(search);
const iterator = searchParams.keys();
const obj = {};

for (let item of iterator) obj[item] = searchParams.get(item);
idEl.textContent = obj.val;

let n = +localStorage.getItem('keyOnly');
const channel = new BroadcastChannel('keyOnly');

if (isNaN(n)) n = 0;
n += 1;
localStorage.setItem('keyOnly', n);
window.addEventListener('unload', () => {
    let n = +localStorage.getItem('keyOnly');

    if (isNaN(n)) n = 1;
    n -= 1;
    localStorage.setItem('keyOnly', n);
});
channel.addEventListener('message', ({ data: { val } }) => {
    idEl.textContent = val;
});

1、const search = window.location.search;获取当前页面的查询字符串部分。
2、const searchParams = new URLSearchParams(search);使用URLSearchParams解析查询字符串。
3、const iterator = searchParams.keys();获取查询参数的键的迭代器。
4、const obj = {};创建一个空对象obj用于存储查询参数。
5、for (let item of iterator) obj[item] = searchParams.get(item);遍历查询参数的键,并将它们及其对应的值存储在obj对象中。
6、idEl.textContent = obj.val;将obj对象中val键对应的值设置为idEl元素的文本内容。
7、let n = +localStorage.getItem('keyOnly');localStorage中获取名为keyOnly的项,并尝试将其转换为数字。
8、const channel = new BroadcastChannel('keyOnly');创建一个新的BroadcastChannel对象,用于接收消息。
9、if (isNaN(n)) n = 0; n += 1; localStorage.setItem('keyOnly', n);如果localStorage中的值不存在或不是数字,则将其设置为1,并增加计数,用于计算当前打开的页面数。
10、window.addEventListener('unload', () => { ... });添加一个事件监听器,当页面卸载时(例如,用户关闭标签页或导航到另一个页面),减少计数并更新localStorage
11、channel.addEventListener('message', ({ data: { val } }) => { ... });添加一个事件监听器,当通过BroadcastChannel接收到消息时,将消息内容设置为idEl元素的文本内容。


总结

发送页面通过BroadcastChannel发送消息,接收页面通过BroadcastChannel接收消息。发送页面在发送消息前会检查localStorage中的计数,如果计数为0,则通过打开新窗口的方式发送消息;否则,通过BroadcastChannel发送消息。接收页面在接收到消息后,会更新页面上的idEl元素的文本内容。
此外,还跟踪接收页面打开的次数,当页面卸载时减少计数,并在每次打开新的接收页面时增加计数。这样可以确保发送页面在发送消息时,可以根据计数决定使用BroadcastChannel发送还是打开新窗口发送。


localStorage方式(叁)

叙言

以下代码展示两个页面之间的通信机制,一个页面发送消息,另一个页面接收消息。发送页面使用localStoragewindow.open来发送消息,接收页面使用localStoragewindow.addEventListener来接收消息。


发送页(send)

const liEl = document.querySelectorAll('.item');
const val = JSON.parse(localStorage.getItem('pageItem'))?.val || 1;

handle(val);

function handle(val) {
    const n = +localStorage.getItem('keyOnly');
    const pageItem = { val, type: 'keyOnly' };
    const setItem = (data) => localStorage.setItem('pageItem', JSON.stringify(data));

    if (!isNaN(n) && n > 0) {
        setItem(pageItem);
    } else {
        setItem(pageItem);
        window.open('./receive.html', '_blank');
    }

    liEl.forEach(item => {
        if (item.textContent == val) {
            item.setAttribute('data-activa', '');
        } else {
            item.removeAttribute('data-activa');
        }
    });
}

window.addEventListener('storage', ({ key, storageArea, newValue }) => {
    if (storageArea && key === 'keyOnly') {
        if (newValue == 0) {
            liEl.forEach(item => {
                item.removeAttribute('data-activa');
            });
            localStorage.removeItem('pageItem');
            localStorage.removeItem('keyOnly');
        }
    }
});

1、const liEl = document.querySelectorAll('.item');选择页面上所有类名为item的元素。
2、const val = JSON.parse(localStorage.getItem('pageItem'))?.val || 1;localStorage中获取名为pageItem的项,并尝试将其解析为JSON对象。如果解析失败或不存在,则val默认为1。
3、handle(val);调用handle函数并传入val作为参数。
4、function handle(val) { ... }定义handle函数,用于处理发送消息的逻辑。
5、const n = +localStorage.getItem('keyOnly');localStorage中获取名为keyOnly的项,并尝试将其转换为数字。
6、const pageItem = { val, type: 'keyOnly' };创建一个对象pageItem,包含val和type属性。
7、const setItem = (data) => localStorage.setItem('pageItem', JSON.stringify(data));定义一个函数setItem,用于将对象转换为JSON字符串并存储到localStorage中。
8、if (!isNaN(n) && n > 0) { ... } else { ... }如果localStorage中的keyOnly项存在且为正数,则调用setItem函数存储pageItem对象;否则,打开./receive.html页面。
9、liEl.forEach(item => { ... });遍历所有item元素,如果元素的文本内容与val相等,则设置data-activa属性;否则,移除该属性。
10、window.addEventListener('storage', ({ key, storageArea, newValue }) => { ... });添加一个事件监听器,当localStorage发生变化时触发。如果变化的键是keyOnly且新值为0,则移除所有item元素的data-activa属性,并清除pageItem和keyOnly的存储。


接收页(receive)

let pageItem = localStorage.getItem('pageItem');
let n = +localStorage.getItem('keyOnly');
let setEl = (info) => {
    info = JSON.parse(info);
    idEl.textContent = info.val;
};

setEl(pageItem);
window.addEventListener('storage', ({ key, storageArea, newValue }) => {
    if (storageArea && key === 'pageItem') setEl(newValue);
});
if (isNaN(n)) n = 0;
n += 1;
localStorage.setItem('keyOnly', n);
window.addEventListener('unload', () => {
    let n = +localStorage.getItem('keyOnly');

    if (isNaN(n)) n = 1;
    n -= 1;
    localStorage.setItem('keyOnly', n);
});

1、let pageItem = localStorage.getItem('pageItem');localStorage中获取名为pageItem的项。
2、let n = +localStorage.getItem('keyOnly');localStorage中获取名为keyOnly的项,并尝试将其转换为数字。
3、let setEl = (info) => { ... };定义一个函数setEl,用于将传入的JSON字符串解析并更新页面上的idEl元素的文本内容。
4、setEl(pageItem);调用setEl函数,传入pageItem,更新页面。
5、window.addEventListener('storage', ({ key, storageArea, newValue }) => { ... });添加一个事件监听器,当localStorage发生变化时触发。如果变化的键是pageItem,则调用setEl函数更新页面。
6、if (isNaN(n)) n = 0; n += 1; localStorage.setItem('keyOnly', n);如果localStorage中的keyOnly项不存在或不是数字,则将其设置为0,并增加计数。
7、window.addEventListener('unload', () => { ... });添加一个事件监听器,当页面卸载时触发。如果localStorage中的keyOnly项存在且为数字,则将其减1。


总结

发送页面通过localStoragewindow.open发送消息,接收页面通过localStoragewindow.addEventListener接收消息。发送页面在发送消息前会检查localStorage中的计数,如果计数为0,则通过打开新窗口的方式发送消息;否则,通过localStorage发送消息。接收页面在接收到消息后,会更新页面上的idEl元素的文本内容。
此外,接收页面还跟踪发送页面发送消息的次数,当页面卸载时减少计数,并在每次发送消息时增加计数。这样可以确保发送页面在发送消息时能够根据计数决定是通过localStorage发送还是通过打开新窗口发送。


三种方式的区别

1、方式壹和方式贰的区别
1.1、方式壹通过自定义窗口(页面)名称来打开新窗口,并通过自定义名称检测是否已有相同名称的窗口(页面)存在;没有则打开新窗口(页面),有则刷整个窗口(页面)。
1.2、方式贰通过本地存储的keyOnly判断是否第一次打开新窗口(页面),如果是就直接使用window.open打开页面,否则通过BroadcastChannel传参给接收页面,不用再次打开新窗口(页面)。
1.3、两种方式的传参区别,方式壹通过URL传参,并且自定义窗口(页面)名称,使其永远只打开一个相同名称的窗口(页面),但用户体验不是很好,因为每次打开都刷新整个页面(窗口);方式贰首次打开通过URL传参,第二次传参则通过BroadcastChannel实现,此操作对接收页面的代码编写不友好,接收页面需要编写两套代码来实现数据接收的工作,第一套是通过URL接收参数;第二套是通过BroadcastChannel接收参数。


2、方式贰和方式叁的区别
2.1、方式贰的实现本质已在第一大点中叙述,此处不在赘叙。
2.2、方式叁通过本地缓存的方式实现参数的传递;第一次打开新窗口之前先把参数缓存在本地,接收页加载的时候直接从缓存中获取即可;再次传参也是本地缓存,但接收页面则通过监听缓存变化来获取参数。


3、三种方式各自的优缺点
优点
方式壹,较好的解决窗口多开的问题;
方式贰,解决页面整体刷新的问题;
方式叁,解决接收页面写两套接收代码的问题。
缺点
方式壹,每次传参都会整体刷新页面,用户体验感不好;
方式贰,传参的方式复杂,接收参数也麻烦,首次打开时通过URL传参,二次传参则通过BroadcastChannelAPI;此方式增加接收页的代码量和代码逻辑复杂度。并且不能像方式壹一样很好的解决窗口多开问题;
方式叁,不能像方式壹一样很好的解决窗口多开问题,有时候会发生多开的情况。


4、总结
如果侧重防多开功能,建议使用第一种方式;
如果侧重传参和用户体验,建议使用第三种方式;
第三种方式存有可优化空间(待优化…),视情况选择合适自己的方式。


彩蛋

1、web前端实现多个元素标签页相互通信、html页面之间相互通信

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14493.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!