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>
;传递的参数值都会转成字符串,比如null
或undefined
都会转成'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预处理器)编写一些样式和一个名为
bsbb
的mixin
(混合器)。
1、
@mixin bsbb()
定义一个名为bsbb
的mixin,它设置box-sizing
属性为border-box
。box-sizing: border-box;
确保元素的宽度和高度包括内容、内边距和边框,而不是仅仅包括内容。
2、html, body
选择器设置html
和body
元素的margin
和padding
为0px
,并调用bsbb
混合器。
3、body
选择器设置body
元素的padding
为68px
,并再次调用bsbb
混合器。
4、.box
选择器定义.box
类的样式,它包含一个.item
类的子元素。.box > .item
选择器设置.item
的样式,包括鼠标指针样式、无列表项标记、内边距、box-sizing
、文本居中、字体大小、字体粗细、背景颜色和边框圆角。
5、.box > .item:not(:first-child)
选择器设置除了第一个.item
之外的所有.item
的margin-top
为18px
。
6、.box > .item:hover
选择器定义鼠标悬停在.item
上时的背景颜色。
7、.box > .item:active
选择器定义当.item
被激活(例如点击)时的背景颜色。
8、.box > .item[data-activa]
选择器定义当.item
元素具有data-activa
属性时的文本颜色和背景颜色。
9、#idEl
选择器定义具有id
为idEl
的元素样式,它设置高度和行高为视口高度减去136px
,margin
和padding
为0px
,文本居中,字体大小为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.open
或window.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、作用域
localStorage
和sessionStorage
都基于源(origin),这意味着它们只能访问相同协议、域名和端口的数据。不同源之间的数据存在隔离(跨域)。localStorage
的数据在所有同源的窗口和标签页中共享,而sessionStorage
的数据仅限于创建它的窗口或标签页。
3、存储大小
localStorage
和sessionStorage
的存储大小限制因浏览器而异,但通常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监听不被触发的问题
如果在两个页面中,只有使用
localStorage
时storage
事件才会被触发,而使用sessionStorage
时不会,这可能是因为storage
事件的触发机制和sessionStorage
的特性导致。
storage
事件被触发的条件
1、当localStorage
或sessionStorage
中的任何数据被修改时(例如,通过setItem
、removeItem
或clear
方法)。
2、事件会在所有同源的页面中触发,无论这些页面是打开在同一个标签页还是不同的标签页。
sessionStorage
的特性
1、sessionStorage
与特定的浏览器标签页或窗口关联。这意味着,如果在标签页A中修改sessionStorage
,只有标签页A会接收到storage
事件。其他标签页(即使它们是同一个源)不会接收到这个事件,因为它们的sessionStorage
独立。
2、当标签页关闭时,与该标签页关联的sessionStorage
会被清除。
据上,如果在两个不同的标签页中分别修改sessionStorage
,每个标签页只会接收到自己修改的事件,而不会接收到另一个标签页的事件。这就是为什么在两个页面中,只有使用localStorage
时storage
事件才会被触发的原因。
如果希望在两个标签页中都能监听到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¶m2=value2
,那么search的值将是?param1=value1¶m2=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方式(贰)
叙言
以下代码展示两个页面之间的通信机制,一个发送消息页面,一个接收消息页面。使用
BroadcastChannel
API来实现跨页面通信,以及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方式(叁)
叙言
以下代码展示两个页面之间的通信机制,一个页面发送消息,另一个页面接收消息。发送页面使用
localStorage
和window.open
来发送消息,接收页面使用localStorage
和window.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。
总结
发送页面通过
localStorage
和window.open
发送消息,接收页面通过localStorage
和window.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传参,二次传参则通过BroadcastChannel
API;此方式增加接收页的代码量和代码逻辑复杂度。并且不能像方式壹一样很好的解决窗口多开问题;
方式叁,不能像方式壹一样很好的解决窗口多开问题,有时候会发生多开的情况。
4、总结
如果侧重防多开功能,建议使用第一种方式;
如果侧重传参和用户体验,建议使用第三种方式;
第三种方式存有可优化空间(待优化…),视情况选择合适自己的方式。
彩蛋
1、web前端实现多个元素标签页相互通信、html页面之间相互通信