先說說我為什麼有這種“奇怪”的想法。
它基于這樣一個場景:我最近閑來無事完善了一個小demo:音樂播放器。在裡面有一個功能 —— 點選清單某一項彈出音樂播放彈框。我原先一直是“為每一項單獨加一個click事件監聽”。這很糟糕!
<div id="videob"></div>
//js代碼
videob.onclick=function(){
let video=document.createElement('video')
video.setAttribute('src','xxx')
video.setAttribute('controls','controls')
video.style.cssText="width:90%;height:300px"
document.querySelector('body').appendChild(video)
}
後來我優化時改用了 事件代理 ,不得不承認它非常優秀,但是我不得不改變結構,使得js中出現了一堆“MP4資料”(一個數組),在觸發事件時判斷目前是哪一個,然後從數組中拿到指派給
audio
的
src
。
事實上,在開發 Web 應用時,經常會用 JavaScript 擷取文檔之外的資訊
幸運的是,HTML5引入了自定義資料屬性:所有的自定義資料屬性都以 data-字首開頭,HTML5 文檔的驗證器會在驗證時忽略它。開發人員可以在任意元素中加入自定義資料屬性,屬性值可以是照片的中繼資料、經緯度坐标或者彈出視窗的尺寸。
最棒的是,幾乎在所有浏覽器中,你都能夠使用自定義資料屬性,因為我們可以輕易地使用 JavaScript 來擷取它們。
為什麼設定onclick不好
在開始操作之前,我們必須先“重複”一個概念:0級DOM事件 。通俗來說就是各種on字首的事件名稱(比如:onclick、onmouseon、onkeydown),他們可以存在于
<script>
或者HTML标簽中,且沒有事件捕獲和冒泡機制!
有這樣一個場景:Web 應用的開發人員尤其是初學者常常需要依賴于彈出視窗,以便向使用者顯示線上幫助資訊、附加選項或者其他重要的使用者界面功能。
我們經常能在一個頁面中看到如下代碼:
<a href="#" onclick="window.open('xxx',null,'width=300,height=300');">go to xxx</a>
就是這段沒怎麼起眼的代碼存在着非常緻命的問題:
- 連結的目标位址沒有設定!如果 JavaScript 被禁用了,那麼連結将無法引導使用者進入相應頁面。這很一個非常嚴重,以至于我們需要立即解決。我建議開發者永遠不要省略 href屬性、任何情況下都不要為 href 屬性賦“#”。
- 注意保持行為與内容分離,正如用連結樣式表保持樣式資訊分離一樣。開始的時候,使用 onclick 會帶來便利,但是想象一下頁面上有 50 個連結的情況,那時你會看到 onclick 方法失控的場面。你将隻能一遍又一遍地編寫重複的 JavaScript 代碼。如果是通過伺服器端代碼來生成浏覽器端代碼,那麼你就是在增加 JavaScript 事件。
為了解決上面的問題,我們為每個a連結設定了href,而且增加了不同的class!以增加“相同類型元素”的可識别性。
<a href="xxx" class="pop">go to xxx</a>
//js代碼
var link=document.querySelector('a.pop')
link.onclick=function(e){
e.preventDefault();
window.open(this.getAttribute('href'));
}
我們還沒有為
open
設定長寬!
現在,我們又回到了和文章開頭一樣的問題!
使用自定義資料屬性
當建立應用了 JavaScript 的 Web 應用時,剛剛提到的情況比較常見。如你所見,在代碼中存儲視窗的期望高度和寬度是可取的,但是 onclick 方法有諸多弊端。我們可以改換在元素上嵌入屬性的方式加以實作。現在要做的是将連結改造成下面這種形式:
<a href="xxx" data-width="600" data-height="400" class="pop">go to xxx</a>
var link=document.querySelector('a.pop')
link.onclick=function(e){
e.preventDefault();
window.open(this.getAttribute('href'),null,"width="+this.getAttribute('data-width')+",height="+this.getAttribute('data-height'));
}
好吧,這個例子似乎沒有什麼說服力。
但是為了讓你能夠真正認識到“問題的嚴重性”,我将帶你來優化文章開頭的示例:
<div id="mxc">
<div class="videob" data-uri="./img/1.mp4"></div>
<div class="videob" data-uri="./img/2.mp4"></div>
</div>
mxc.addEventListener('click',function(e){
let target=e.target || e.srcElement;
let video=document.createElement('video')
video.setAttribute('src',target.getAttribute('data-uri'))
video.setAttribute('controls','controls')
video.style.cssText="width:90%;height:300px;margin:0 auto"
document.querySelector('body').appendChild(video)
},false)
這樣才“真正”展現了“代理的價值”!
2020-09-09更新
發現一個沒怎麼注意的事情:父元素事件中的
e.target
是直接指向了“目前觸發事件的子元素的最底層子元素”,其上面的“父元素”都将以parentNode的形式被展示出來(原型上)。
發現這個是因為想要換一種思路寫一個“類原生相冊”,我的思路是:開始時總的寬度是100vw,子元素(相冊圖檔)以flex排列;點選某一個圖檔預覽時動态給父元素添加一個類名,這個類的作用是:将父元素下(單張)圖檔的最大寬度設為100vw(子元素從始至終都不設寬)。然後在js中根據子元素的長度計算其“真正寬度”(
.style.width
)。并根據目前點選的是第幾個子元素計算應該transform偏移多少距離?
其核心代碼:
//計算目前點選元素是父元素中的第幾個子元素 == jQuery:index()
var child = document.getElementById(xxx);
var index = Array.prototype.indexOf.call(child.parentNode,child);
其他用到“自定義資料屬性”最多的地方應該就是【圖檔懶加載】裡面設定替換圖了吧:
<body>
<div class="imgs">
<div>
<img src="占位圖" data-src="真正圖檔" alt="圖檔說明">
</div>
...
</div>
<script>
window.onload=function () {
var imgs=document.querySelectorAll('img');
function offset(el){
let top=el.offsetTop
while(el.offsetParent){
el=el.offsetParent
top+=el.offsetTop //scrollTop:設定或擷取位于對象最頂端和視窗中可見内容的最頂端之間的距離
}
return { top }
}
function lazyload(imgs) {
var h=window.innerHeight; //innerheight 傳回視窗的文檔顯示區的高度
var s=document.documentElement.scrollTop || document.body.scrollTop; //document.body.scrollTop 網頁被卷去的高
for(var i=0;i<imgs.length;i++){
if((h+s)>offset(imgs[i]).top){ //視窗文檔顯示去高度+向上滾動的高度,判斷是否到達了圖檔的位置,如果到了,才去加載,并在加載中 用一個動圖去“占位”
(function (i) {
setTimeout(function () {
var tmp=new Image();
tmp.src=imgs[i].getAttribute("data-src"); // 直接擷取
tmp.onload=function () {
imgs[i].src=imgs[i].getAttribute("data-src"); // 如果直接擷取不了(還沒加載完),就等加載完前面的再去擷取
}
},1300)
})(i)
}
}
}
lazyload(imgs);
window.onscroll=function () {
lazyload(imgs);
}
}
</script>
</body>