件被看作是 JavaScript 與網頁之間交互的橋梁,當事件發生時,可以通過 JavaScript 代碼(函數)執行相關的操作。例如,用戶可以通過鼠標拖曳登錄框,改變登錄框的顯示位置;或者在閱讀文章時,選中文本后自動彈出分享、復制選項。本章將對 DOM 中的事件進行詳細講解。
事件可被理解為是 JavaScript 偵測到的行為,這些行為指的就是頁面的加載、鼠標單擊頁面、鼠標滑過某個區域、按下鍵盤等具體的動作,它對實現網頁的交互效果起著重要的作用。在深入學習事件時,需要對一些非常基本又相當重要的概念有一定的了解。
事件處理程序指的就是 JavaScript 為響用戶行為所執行的程序代碼。例如,用戶單擊 button 按鈕時,這個行為就會被 JavaScript 中的click 事件偵測到;然后讓其自動執行,為 click 事件編寫的程序代碼,如在控制臺輸出“按鈕被單擊了”。
事件驅動是指,在 Web 頁面中 JavaScript 的事件,偵測到的用戶行為(如鼠標單擊、鼠標移入等),并執行相應的事件處理程序的過程。
事件綁定指的是為某個元素對象的事件綁定事件處理程序。在 DOM 中提供了3種事件的綁定方式。下面將針對以3種事件綁定方式的語法以及各自的區別進行詳細講解。
事件的行內綁定式是通過HTML標簽的屬性設置實現的,具體語法格式如下。
<div onclick="alert('handle click')"></div>
在上述語法中,div 可以是任意的HTML標簽,如 <button>、<input>標簽等;事件是由 on 和事件名稱組成的一個 HTML 屬性,如單擊事件對應的屬性名為 onclick;事件的處理程序指的是 JavaScript 代碼,如匿名函數等。
需要注意的是,由于開發中提倡 JavaScript 代碼與 HTML 代碼相分離。因此,不建議使用行內綁定事件。
動態的綁定方式很好地解決了JavaScript代碼與HTML代碼混合編寫的問題。在JavaScript代碼中,為需要事件處理的 DOM 元素對象,添加事件與事件處理程序。具體語法格式如下。
div.onclick=function handleClick () {
console.log('handle click');
};
在上述語法中,事件的處理程序一般都是匿名函數或有名的函數。在實際開發中,相對于行內綁定來說,事件的動態綁定的使用居多。
行內綁定與動態綁定除了實現的語法不同以外,本質是相同的,都是給 DOM 元素綁定了一個 onclick 屬性。
為了給同一個 DOM 對象的同一個事件添加多個事件處理程序,DOM中引入了事件流的概念,可以讓DOM對象通過事件監聽的方式實現事件的綁定。由于不同瀏覽器采用的事件流實現方式不同,事件監聽的實現存在兼容性問題。通常根據瀏覽器的內核可以劃分為兩大類,一類是早期版本的IE瀏覽器(如IE6~8),一類遵循W3C標準的瀏覽器(以下簡稱標準瀏覽器)。
接下來,將根據不同類型的瀏覽器,分別介紹事件監聽的實現方式。
(1)早期版本的IE瀏覽器
在早期版本的IE瀏覽器中,事件監聽的語法格式如下。
DOM對象.attachEvent(type,callback);
在上述語法中,參數 type 指的是為 DOM 對象綁定的事件類型,它是由 on 與事件名稱組成的,如 onclick。。參數 callback 表示事件的處理程序。
(2)標準瀏覽器
標準瀏覽器包括IE8版本以上的IE瀏覽器(如IE9~11),新版的Firefox、Chrome等瀏覽器。具體語法格式如下。通過這種方式我們可以給元素注冊多個事件處理函數,而 btn.onclick=fn 是賦值操作只能設置一個事件處理函數。
DOM對象.addEventListener(type, callback, [capture]);
在上述語法中,參數 type 指的是 DOM 對象綁定的事件類型,它是由事件名稱設置的,如 click。參數 callback 表示事件的處理程序。參數 capture 默認值為 false,這個屬性后面單獨介紹,一般情況我們都使用它的默認值。
現在 IE 瀏覽器已經被淘汰,所以我們不需要再去記憶 attachEvent() 的用法,但是我們需要了解過去,過去在使用這種方式注冊事件的時候需要處理瀏覽器的兼容性,下面我們演示下:
function addEventListener(element, type, listener) {
// 能力檢測: 就是要看當前的瀏覽器是否支持此標簽對象的屬性或是方法
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, listener);
} else {
element['on' + type]=listener;
}
}
在事件處理函數中的 this 指向當前觸發該事件的 DOM 元素。
link.onclick=function handleLink () {
photo.src=this.href;
p.textContent=this.title;
return false;
};
但是通過行內綁定注冊的事件,調用的函數中 this 指向的是 window。
<button onclick="handle()">按鈕</button>
<script>
function handle () {
// 此處的 this 指向 window
console.log(this);
}
</script>
行內綁定事件 onclick="handle()" 中的 "" 雙引號內部其實可以看做是一個匿名函數,"" 雙引號內部的這個匿名函數才是事件處理函數,在事件處理函數中又調用了 handle() 方法。
<!-- 此處的 this 指向的是觸發事件的對象 button -->
<button onclick="handle(this)">按鈕</button>
<script>
function handle (btn) {
// 此處的 this 指向 window
console.log(btn);
}
</script>
綁定事件的元素可以解除綁定,例如:我們可以讓按鈕點擊一次之后解除事件綁定。三種綁定事件的解除事件的方式不同,下面我們分別來介紹。
行內綁定事件和動態綁定事件本質上都是給 DOM 元素設置 onclick 屬性,對應的解除綁定事件的方式都是把 onclick 屬性重新設置為 null。
當按鈕執行完點擊事件的處理程序后立即解除事件的綁定
<button onclick="handle(this)">按鈕</button>
<script>
function handle (btn) {
alert('Hello');
btn.onclick=null;
}
</script>
btn.onclick=function handle () {
this.onclick=null;
};
標準綁定事件使用 addEventListener(type, callback, [capture]); 方法,對應的解除綁定使用的方法是 removeEventListener(type, callback, [capture]),需要注意的是,如果注冊的事件需要解除的話,使用 addEventListener() 注冊事件的時候,傳入的 callback 不能是匿名函數,因為解除事件綁定的時候還需要引用這個函數。
const div=document.querySelector('#div');
div.addEventListener('click', handle);
function handle () {
alert('hello');
this.removeEventListener('click', handle);
}
我們已經會使用 addEventListener(type, callback, [capture]),方法給元素注冊事件,但是這個方法的第三個參數的作用我們還不清楚,下面我們就來介紹該方法的第三個參數,這里我們需要先來學習 DOM 中的事件流(事件模型)。
DOM (文檔對象模型)結構是一個樹型結構,當一個HTML元素產生一個事件時,該事件會在元素結點與根節點之間按特定的順序傳播,路徑所經過的節點都會收到該事件,這個傳播過程可稱為 DOM 事件流。
事件順序有兩種類型:事件捕捉和事件冒泡。
這是 IE 瀏覽器對事件模型的實現,也是最容易理解的。冒泡,顧名思義,事件像個水中的氣泡一樣一直往上冒,直到頂端。
從DOM 樹型結構上理解,就是事件由葉子節點沿祖先結點一直向上傳遞直到根節點;從瀏覽器界面視圖 HTML 元素排列層次上理解就是事件由具有從屬關系的觸發事件的元素一直傳遞到根元素直到文檔對象。
addEventListener(type, callback, [capture]),該方法的第三個參數為 false 的時候設置觸發事件的方式為事件冒泡,該參數默認為 false。
一般情況下,我們都會使用事件冒泡的方式注冊事件。
const outer=document.querySelector('#outer');
const inner=document.querySelector('#inner');
outer.addEventListener('click', function () {
console.log('點擊了 outer');
}, false);
inner.addEventListener('click', function () {
console.log('點擊了 inner');
}, false);
document.body.addEventListener('click', function () {
console.log('點擊了 body');
}, false);
document.addEventListener('click', function () {
console.log('點擊了 document');
}, false);
window.addEventListener('click', function () {
console.log('點擊了 window');
}, false);
執行結果:
使用行內綁定和動態綁定事件的方式默認使用的是事件冒泡。
Netscape 的實現,它與冒泡型剛好相反,由 DOM 樹最頂層元素一直到觸發事件的元素。
addEventListener(type, callback, [capture]),該方法的第三個參數為 true 的時候設置觸發事件的方式為事件捕獲。
const outer=document.querySelector('#outer');
const inner=document.querySelector('#inner');
outer.addEventListener('click', function () {
console.log('點擊了 outer');
}, true);
inner.addEventListener('click', function () {
console.log('點擊了 inner');
}, true);
document.body.addEventListener('click', function () {
console.log('點擊了 body');
}, true);
document.addEventListener('click', function () {
console.log('點擊了 document');
}, true);
window.addEventListener('click', function () {
console.log('點擊了 window');
}, true);
執行結果:
使用行內綁定和動態綁定事件的方式無法使用事件捕獲。
我們已經對上面兩個不同的事件模型進行了解釋和對比。DOM 標準同時支持兩種事件模型,即事件捕獲與事件冒泡,但是,事件捕獲先發生。兩種事件流都會觸發 DOM 中的所有對象,從 document對象開始,也在 document 對象結束(大部分兼容標準的瀏覽器會繼續將事件是捕捉/冒泡延續到window 對象)。
如圖:首先是捕獲傳遞事件,接著是冒泡傳遞,所以,如果一個處理函數既注冊了捕獲型事件的監聽,又注冊冒泡型事件監聽,那么在 DOM 事件模型中它就會被調用兩次。
DOM 標準的事件模型最獨特的性質是,文本節點也會觸發事件(在IE不會)。
事件委托,通俗地來講,就是把一個元素的處理事件的函數委托到另一個元素。
一般來講,會把一個或者一組元素的事件委托到它的父層或者更外層元素上,真正綁定事件的是外層元素,當事件響應到需要綁定的元素上時,會通過事件冒泡機制從而觸發它的外層元素的綁定事件上,然后在外層元素上去執行函數。
舉個例子,比如一個宿舍的同學同時快遞到了,一種方法就是他們都傻傻地一個個去領取,還有一種方法就是把這件事情委托給宿舍長,讓一個人出去拿好所有快遞,然后再根據收件人一一分發給每個宿舍同學。
在這里,取快遞就是一個事件,每個同學指的是需要響應事件的 DOM 元素,而出去統一領取快遞的宿舍長就是代理的元素,所以真正綁定事件的是這個元素,按照收件人分發快遞的過程就是在事件執行中,需要判斷當前響應的事件應該匹配到被代理元素中的哪一個或者哪幾個。
下面我們來做一個練習,為下面的每一個 li 注冊點擊事件,當點擊當前 li 的時候打印 li 中的文本內容。
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
首先我們用傳統的方式來實現,先獲取到頁面上所有的 li,然后遍歷所有的 li,給每一個 li 注冊點擊事件,這里使用 addEventListener() 注冊事件的時候省略了第三個參數,默認為 false,事件冒泡的方式。
這樣做不好的地方有兩點,第一:我們需要為每一個 li 元素創建一個新的事件處理函數,每次創建都需要銷毀時間和內存。第二:當點擊按鈕往 ul 中添加新的 li 元素的時候需要給新創建的 li 注冊點擊事件。
const lis=document.querySelectorAll('#list li');
lis.forEach(function (li) {
li.addEventListener('click', function () {
console.log(this.textContent)
});
});
下面我們使用事件委托的方式優化上面的代碼,把點擊事件注冊給父元素 ul,當點擊 li 的時候通過事件冒泡把點擊事件傳遞給父元素 ul。
const ul=document.querySelector('#list');
ul.addEventListener('click', function () {
console.log('test');
// 此處的 this 是注冊事件的元素 ul
console.log(this);
});
代碼改完之后點擊 li,這段代碼確實可以執行,但是我們的目標是打印 li 之間的內容,而通過打印發現此處的 this 不是我們想要的當前點擊的 li,而是注冊事件的元素 ul。所以這里需要強調一點,在注冊事件的時候,事件源是注冊事件的對象。
那如何獲取當前觸發事件的元素 li 呢?當事件被觸發的時候事件處理函數會接收一個參數,這個參數叫做事件對象,事件對象可以提供觸發事件的時候相關的數據,下一小節詳細介紹,這里我們先用事件對象解決當前的問題,事件對象中有一個 target 屬性,這個屬性就是當前觸發事件的對象。
在 IE 瀏覽器中獲取事件對象的方式不同,IE 中是通過 window.event 獲取事件對象,以前在獲取事件對象的時候還要處理瀏覽器兼容性問題,IE 瀏覽器現在已經被淘汰所以瀏覽器兼容性的處理我們就不再演示。
const ul=document.querySelector('#list');
// 事件參數(對象) e
ul.addEventListener('click', function (e) {
// e.target 觸發事件的元素
console.log(e.target.textContent);
// 注冊事件的元素
console.log(this);
});
到這里這個案例就完成了,我們再來擴展下這個案例,如果想要點擊特定的 li 來觸發事件該如何實現?
<ul id="list">
<li>item 1</li>
<li class="cls">item 2</li>
<li class="cls">item 3</li>
......
<li>item n</li>
</ul>
如上代碼,如果想點擊具有特性類樣式或者特定 id 的元素觸發事件,可以通過判斷當前點擊的元素 e.target 的類樣式或者 id 屬性進行判斷。
if (e.target.className==='cls') {
// ....
}
但是如果想像 CSS 選擇器一樣更加靈活的匹配的話,上面的判斷不夠靈活,這里可以使用 元素.matches(選擇器) 來匹配特定元素。當元素匹配指定的選擇器返回 true。
const ul=document.querySelector('#list');
ul.addEventListener('click', function (e) {
// matches 方法,當元素匹配指定的選擇器返回 true
if (e.target.matches('.cls')) {
console.log(e.target.textContent);
}
});
利用事件冒泡的特性,將本應該注冊在子元素上的處理事件注冊在父元素上,這樣點擊子元素時發現其本身沒有相應事件就到父元素上尋找作出相應。這樣做的優勢有:1. 減少內存消耗,避免重復創建相同事件處理函數,只需要把多個子元素的事件委托給父元素。2.隨時可以添加子元素,添加的子元素會自動有相應的處理事件。
const tbody=document.querySelector('tbody');
tbody.addEventListener('click', function (e) {
// 注冊事件的元素 tbody
// console.log(this);
// 觸發事件的元素(你點擊的那個元素)
// console.log(e.target)
// 判斷元素是否是指定的元素
// console.log(e.target.matches('.del'))
if (e.target.matches('.del')) {
e.target.parentNode.parentNode.remove();
}
});
每當觸發一個事件,就會產生一個事件對象 event,該對象包含著所有與事件有關的信息。包括導致事件的元素、事件的類型以及其他與特定事件相關的信息。上一小節中我們使用事件對象獲取觸發事件的元素。
例如:鼠標操作產生的 event中會包含鼠標位置的信息;鍵盤操作產生的event中會包含與按下的鍵有關的信息。
所有瀏覽器都支持 event 對象,但支持方式不同,在標準 DOM 中 event 對象必須作為唯一的參數傳給事件處理函數,在 IE 中 event 是 window 對象的一個屬性。
成員 | 描述 | 備注 |
type | 觸發的事件名稱 | |
eventPhase | 事件流在傳播階段的位置 | |
target | 觸發事件的元素 | |
srcElement | target 的別名,老版本的 IE 中使用 | |
clientX / clientY | 基于瀏覽器的可視區域,鼠標坐標值 | 可配合固定定位,基于窗口定位 |
pageX / pageY | 基于整個頁面,頁面滾動有關,鼠標在頁面的坐標值 | 可配合絕對定位,基于頁面定位 |
key | 獲取按鍵輸入 | |
preventDefault() | 取消默認行為 | |
stopPropagation() | 阻止事件冒泡 |
const img=document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
// 鼠標位置 - 圖片大小的一半
img.style.left=e.clientX - 96 / 2 + 'px';
img.style.top=e.clientY - 80 / 2 + 'px';
});
設置樣式,讓 body 的高度等于 1500px(垂直方向出現滾動條),滾動條下拉這時候移動鼠標,圖片的縱向位置跟鼠標脫離。
原因是 clientX 和 clientY 獲取的是鼠標在當前可視區域的位置。如果出現滾動條的話可以通過 pageX 和 pageY 獲取鼠標在當前文檔中的位置。
const img=document.querySelector('#img');
document.addEventListener('mousemove', function (e) {
img.style.left=e.pageX - 96 / 2 + 'px';
img.style.top=e.pageY - 80 / 2 + 'px';
});
這里獲取圖片大小的時候寫的是具體值,將來圖片替換后,還需要改變這里的大小。我們可以使用 getComputedStyle() 獲取圖片的大小。
const img=document.querySelector('#img');
img.addEventListener('load', function () {
const style=window.getComputedStyle(img, null);
const imgWidth=parseInt(style.width);
const imgHeight=parseInt(style.height);
document.addEventListener('mousemove', function (e) {
img.style.left=e.pageX - imgWidth / 2 + 'px';
img.style.top=e.pageY - imgHeight / 2 + 'px';
});
});
注意:這里需要在 img 標簽加載完畢后獲取圖片的大小,否則獲取到的圖片大小是 0,因為 load 事件代表圖片被加載,否則的話代碼從上到下執行到這個位置,圖片還沒有被下載回來,這個時候獲取圖片的大小是 0。
#img {
width: 100px;
position: absolute;
left: 0;
top: 0;
}
.toLeft {
transform: rotateY(180deg);
}
const img=document.querySelector('#img');
let x=0;
let y=0;
document.addEventListener('keydown', function (e) {
switch (e.key) {
case 'ArrowLeft':
x -=10;
img.classList.add('toLeft');
break;
case 'ArrowRight':
x +=10;
img.classList.remove('toLeft');
break;
case 'ArrowUp':
y -=10;
break;
case 'ArrowDown':
y +=10;
break;
}
img.style.left=x + 'px';
img.style.top=y + 'px';
});
// contextmenu 鼠標右鍵事件
document.addEventListener('contextmenu', function(e) {
// 禁止點擊的默認行為,即顯示上下文菜單
e.preventDefault()
});
// 禁止選中文字事件
document.addEventListener('selectstart', function(e) {
// 禁止選中文字的默認行為,即不能選中文字
e.preventDefault()
})
const loginBg=document.querySelector('#bg');
const loginLink=document.querySelector('#link');
const loginBox=document.querySelector('#login');
const closeBtn=document.querySelector('#closeBtn');
const loginTitle=document.querySelector('#title');
loginLink.addEventListener('click', function () {
loginBox.style.display='block';
loginBg.style.display='block';
});
closeBtn.addEventListener('click', function () {
loginBox.style.display='none';
loginBg.style.display='none';
});
// 拖動事件的三個過程:鼠標按下 mousedowm,鼠標移動 mousemove,鼠標松開 mouseup
const style=window.getComputedStyle(loginBox, null);
// 模態框跟著鼠標走的原理
loginTitle.addEventListener('mousedown', function (e) {
const loginLeft=parseInt(style.left);
const loginTop=parseInt(style.top);
// 步驟一:當鼠標按下時,需要立即得到鼠標在盒子中的坐標
var x=e.pageX - loginLeft;
var y=e.pageY - loginTop;
// 為整個頁面添加鼠標移動事件
document.addEventListener('mousemove', move);
function move(e) {
// 步驟二:模態框的left和top等于鼠標在頁面的坐標減去鼠標在盒子內的坐標
// 注意:一定要加上px
login.style.left=e.pageX - x + 'px';
login.style.top=e.pageY - y + 'px';
}
// 步驟三:鼠標松開時取消整個頁面的鼠標移動事件
document.addEventListener('mouseup', function (e) {
document.removeEventListener('mousemove', move);
});
});
在這之前我們已經使用過了單擊事件、鼠標經過和鼠標離開的事件,瀏覽器給我們提供的事件種類非常多,下面我們列出一些常用的事件,使用的方式都是一樣的。
描述 | 事件名稱 |
鼠標單擊 | click |
鼠標雙擊 | dblclick |
鼠標移入 | mouseover |
鼠標移出 | mouseout |
鼠標移動 | mousemove |
獲取焦點 | focus |
失去焦點 | blur |
鍵盤按下 | keydown |
鍵盤彈起 | keyup |
不能識別功能鍵 ctrl、alt 等 | keypress |
文本框的輸入事件 | input |
const search=document.querySelector('#search');
function hanldeFocus(e) {
if (e.key==='s') {
search.focus();
e.preventDefault();
}
}
document.addEventListener('keydown', hanldeFocus);
search.addEventListener('focus', function () {
document.removeEventListener('keydown', hanldeFocus);
});
search.addEventListener('blur', function () {
document.addEventListener('keydown', hanldeFocus);
});
動端的點透:當上層元素發生點擊的時候, 下層元素也有點擊(焦點)特性, 在300ms之后, 如果上層元素消失或者隱藏,
目標點就會"漂移"到下層元素身上, 就會觸發點擊行為。
<a href="http://baidu.com">百度一下</a>
<p>pc點擊事件</p>
<div></div>
<script type="text/javascript">
//pc端事件是可以在移動端使用的 但是存在一些問題(事件點透)-pc端事件比移動端事件慢300ms
var oDiv=document.getElementsByTagName("div")[0];
var oP=document.getElementsByTagName("p")[0];
oP.onclick=function(){ //pc端事件
this.style.color="orange";
}
oDiv.addEventListener("touchend",function(){ //移動端事件
this.style.display="none";
},true);
</script>
如果我們在百度一下PC點擊事件上方進行點擊會發現當紅色區域隱藏后還會觸發其自身的事件。這種現象就是事件點透。
成因
我們上面說過, 移動設備能夠響應click事件, 不過比較慢, 這是為什么?
因為click事件觸發之后, 要等200ms到300ms左右, 因為瀏覽器有一些默認的手指快捷操作, 比如:快速雙擊兩次屏幕視口會放大, 彈出輔助菜單等等。
大家用手機上網的時候, 尤其看一些PC端網站, 雙擊屏幕, 當前視口就變大了。
本質上:PC端事件比移動端的事件略慢, 大概是在300ms左右。所以我們也稱這種現象為300毫秒延遲。
實例: 事件點透
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
*{
margin: 0;
padding: 0;
list-style: none;
font-size: 5vw;
}
div{
width: 100vw;
height: 50vh;
text-align: center;
background-color: rgba(255,0,0,0.5);
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div></div>
<a href="http://xxxxxx.com">JavaScript</a>
<mark></mark>
<span>我沒有點擊行為</span>
<script type="text/javascript">
var div=document.getElementsByTagName("div")[0];
div.addEventListener("touchstart",function(){
this.style.display="none";
//this.style.backgroundColor="rgba(0,255,0,0.5)";
},false)
var mark=document.getElementsByTagName("mark")[0];
mark.onclick=function(){
this.style.color="red"; //字體變成紅色
}
//事件點透:當上層元素(div)有點擊行為時,當上層元素消失,這種點擊行為會飄到下層(mark)(點擊事件、具有點擊行為)元素身上
//還需要注意上層元素是移動端事件 下層元素如果執行事件必須是PC端事件
//原因:300ms延遲
</script>
</body>
</html>
解決方法: 在移動端中全部使用移動端事件代替PC端事件 ontouchstart
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
*{
margin: 0;
padding: 0;
list-style: none;
font-size: 5vw;
}
div{
width: 100vw;
height: 50vh;
text-align: center;
background-color: rgba(255,0,0,0.5);
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div></div>
<a href="http://xxxxxx.com">JavaScript</a>
<mark>JavaScript</mark>
<script type="text/javascript">
var div=document.getElementsByTagName("div")[0];
div.addEventListener("touchstart",function(){
this.style.display="none";
//this.style.backgroundColor="rgba(0,255,0,0.5)";
},false)
var mark=document.getElementsByTagName("mark")[0];
//在移動端中全部使用移動端事件代替PC端事件
mark.ontouchstart=function(){
this.style.color="red";
}
//事件點透:當上層元素有點擊行為時,當上層元素消失,這種點擊行為會飄到下層(點擊事件、具有點擊行為)元素身上
//還需要注意上層元素是移動端事件 下層元素如果執行事件必須是PC端事件
//原因:300ms延遲
</script>
</body>
</html>
超鏈接如何解決點透
文/ 觀察者網專欄作者 陳藍】
近幾天,中國空間站兩次緊急規避星鏈衛星事件成為熱搜話題,在網上迅速發酵。
事出本月3日,中國通過聯合國和平利用外層空間委員會(COPUOS)向聯合國秘書長提交了一份照會,指出星鏈1095衛星從5月中旬至6月下旬持續降軌至382公里高度后保持在此高度運行。這是一個非常接近中國空間站軌道的高度。7月1日發生第一次近距離接近事件。為保證安全,中國空間站于7月1日晚做了緊急規避。
10月21日,星鏈2305衛星再次接近。這次星鏈衛星處于策略不明的連續機動狀態。中國空間站再次做了避險規避。
這兩次事件發生后,中國官方并未正式宣布,官媒也沒報道,向聯合國發照會同樣也沒官方和官媒消息。是網民從聯合國網站上發現了這個事情。這就是為什么照會發出差不多四周,大家才知道。
不過到目前為止,熱鬧的主要還是中文媒體,尤其是中文自媒體。國外社交網絡和專業圈子有少量討論,而歐美主流媒體幾乎都視而不見。
美國天文學家喬納森·邁克道威爾(Jonathan McDowell)長期關注航天發射和航天器運行狀況,定期發布相關信息和他自己的分析。這次也不例外,他為我們提供了更為詳細的信息。
喬納森·邁克道威爾推特截圖
首先,他認為這兩顆星鏈屬于故障退役衛星。按他的統計,大概有300顆,即占在軌總數16%的星鏈衛星已經離開了550公里的工作軌道。它們都會在SpaceX控制下主動降軌并再入大氣層。鑒于中國空間站處于穩定軌道,喬納森認為SpaceX有主動規避并事先通知中方的責任。
喬納森還對這兩次事件的軌道做了分析。他指出星鏈1095和中國空間站最接近的時刻是7月1日13:15(世界協調時,下同),而中國空間站避險變軌發生在9:50,提前不到三個半小時。我們有理由推測,這是一個突發事件,發現情況時可能已經很遲,采取的是非常緊急的措施。
圖1:7月星鏈1095(藍線)和中國空間站(CSS,紅線)軌道變化歷史(兩根線分別代表近地點和遠地點高度)。
10月21日,中國空間站于3:16變軌。近19個小時后,星鏈2305于22:00接近空間站。這說明,我們對星鏈已經有了警惕,預測比較超前,采取措施的時間也比較充分。
喬納森還提供了兩個航天器的軌道和相鄰距離變化的詳細圖表。圖表顯示,星鏈1095和中國空間站在7月相遇時,星鏈在降軌。它在和中國空間站相遇之際可能做過一些機動。
而10月那次相遇中,星鏈2305在持續升軌,且沒有做任何規避機動。而中國空間站的機動比7月那次更明顯,其遠地點增加、近地點減少,軌道變得更橢圓。
圖2:10月星鏈2305(紅線)和中國空間站(CSS,藍線)軌道變化歷史(兩根線分別代表近地點和遠地點高度)。
圖3:10月事件中,星鏈2305和中國空間站距離變化歷史。
兩個航天器每繞地球一圈就有一次相遇機會。圖3顯示了十月事件的兩次相遇,右側那次相距不到4公里。這在太空中是一個非常近的距離。考慮到在這之前已經做過規避機動,如果不做規避,這個距離肯定更小,不能排除相撞的可能性!所以,這兩個事件的嚴重性無論怎么高估都不為過。
由于航天器飛行速度極高,碰撞造成的后果是毀滅性的。而且,一次碰撞產生的數以萬計的碎片會繼續在軌道上高速運行,進一步提高軌道上的碰撞風險。這種連鎖反應的結果是,軌道上沒有東西能夠幸免。電影《地心引力》已經展現了這個令人恐怖的場景。
太空碰撞毀掉一顆衛星只是經濟損失。但對載人航天來說,代價可能是宇航員的生命。近年來,軌道碎片的數量還在不斷增加,碰撞的風險越來越大。所以,太空垃圾的消減、太空碰撞的預警和規避、載人航天器的太空救生等已經成為非常重要的課題,甚至已經成為政治問題和國際合作的重要內容。
在國際空間站,碰撞規避已經是常規操作。截至2020年底,它在22年的歷史中做過25次緊急規避。而近年來這種操作更為頻繁,平均每年有兩次。
國際空間站目前的標準是,只要發現大于10厘米的太空碎片可能進入國際空間站軌道上下1.25公里、前后25公里的長方形“鞋盒”空間內,或者碰撞概率大于萬分之一,空間站就必須開啟變軌規避,宇航員必須躲進站上對接的載人飛船內,萬一出現意外時可以隨時逃離、返回地球。
國際空間站要求在可能的碰撞前留出五個多小時,其中規避變軌準備約三個小時,等待變軌生效約兩個小時。中國空間站也有類似的規避準則。
然而,這兩次事件的主角并非太空碎片,也非失控航天器,而是尚能由地面控制的衛星。在以往的太空碰撞及“潛在”太空碰撞事件中,總有一方是不受控的太空垃圾或失控航天器。歷來威脅國際空間站的太空垃圾質量都很有限,更沒遭遇過可控航天器的威脅。而這次威脅到中國空間站的是處于可控狀態、260公斤之重的完整衛星,其破壞力可想而知。
毫無疑問,這次事件是非同尋常的。10月事件中星鏈衛星的持續升軌則帶來了更多的疑問。按理說,退役衛星離軌再入是不需要升軌的。
星鏈衛星,圖片來源:SpaceX
SpaceX和馬斯克必須做出解釋,為什么在衛星可控情況下還會發生這樣的事件?為什么降軌規劃時候沒有考慮到和空間站的碰撞?為什么出現風險不提前做規避機動?為什么離軌再入中會出現升軌操作?
截至發稿,SpaceX和馬斯克仍然沒有做任何回應。
筆者不禁聯想到今年中國空間站天和核心艙發射后的情況,當時西方媒體大肆渲染長征五號末級再入地球的風險。事實上,和太空碰撞相比,長五末級再入的可控性更強,風險和長期后果則更低。它的最終再入也如預測的那樣,沒有對地面造成任何損害。
中國空間站
但這次星鏈事件,西方主流媒體幾乎集體失聲(除了《鏡報》這樣的左翼小報),再次顯示了他們所謂的新聞自由和客觀報道的虛偽性。他們對中國的選擇性和雙標報道充滿了傲慢和偏見。
當然,現在對事件的真正原因下結論還為時過早。但即便作最善意的假設,事件也可以引出更多的討論和思考。類似星鏈這樣的巨型星座會帶來什么影響?數萬顆衛星圍繞地球,數百顆衛星在持續降軌,一定還會有失控衛星,太空碰撞的風險絕對不可低估。它對不斷增加的太空人口來說可能是致命的。
目前,星鏈衛星已經占據了近地軌道近距離遭遇事件的一半。當12000顆星鏈衛星入軌后,這一比例預計將增加到90%。
在SpaceX和馬斯克光環的帶動下,很多公司計劃或者已經走上了巨型星座的道路。這種任性的巨型星座和我們星辰大海的理想會不會背道而馳?這是人類正確的方向嗎?地面交通需要交通管理部門、法規和警察的約束和監督,太空難道就不需要嗎?
不過,太空沒有邊界。太空和航天器的管理無法依靠一國來完成。我們需要國際合作、需要基于一致認可的規則的行動。但很遺憾,目前的國際形勢為合作投下了陰影。我們看到太空中出現了越來越多的沖突跡象而不是合作。也許,只有深刻的教訓才能使局面有所改觀。也許,我們只能等待,等待我們的下一代來解決這個難題。
至于馬斯克,如果他要繼續走下去,繼續在太空拓展,最終實現他的火星夢想,他就必須在這一事件上表現出責任感和客觀性,他也必須為巨型星座的問題找到實質性的解決辦法。
讓我們拭目以待吧。
本文系觀察者網獨家稿件,文章內容純屬作者個人觀點,不代表平臺觀點,未經授權,不得轉載,否則將追究法律責任。關注觀察者網微信guanchacn,每日閱讀趣味文章。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。