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

智码帮MJ682517 2024-07-02 14:03:02 阅读 54

MENU

文章技能效果图公共代码和解析style-scssstyle-csshtmlwindow.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实现,此操作对接收页面的代码编写不友好,接收页面需要编写两套代码来实现数据接收的工作,第一套是通过RUL接收参数;第二套是通过BroadcastChannel接收参数。


2、方式贰和方式叁的区别

2.1、方式贰的实现本质已在第一大点中叙述,此处不在赘叙。

2.2、方式叁通过本地缓存的方式实现参数的传递;第一次打开新窗口之前先把参数缓存在本地,接收页加载的时候直接从缓存中获取即可;再次传参也是本地缓存,但接收页面则通过监听缓存变化来获取参数。


3、三种方式各自的优缺点

优点

方式壹,较好的解决窗口多开的问题;

方式贰,解决页面整体刷新的问题;

方式叁,解决接收页面写两套接收代码的问题。

缺点

方式壹,每次传参都会整体刷新页面,用户体验感不好;

方式贰,传参的方式复杂,接收参数也麻烦,首次打开时通过URL传参,二次传参则通过BroadcastChannelAPI;此方式增加接收页的代码量和代码逻辑复杂度。并且不能像方式壹一样很好的解决窗口多开问题;

方式叁,不能像方式壹一样很好的解决窗口多开问题,有时候会发生多开的情况。


4、总结

如果侧重防多开功能,建议使用第一种方式;

如果侧重传参和用户体验,建议使用第三种方式;

第三种方式存有可优化空间(待优化…),视情况选择合适自己的方式。


彩蛋

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。