1.采用原生javaACript 實現全局觸摸按鈕
首先在控制台輸出,觀察事件有哪些關於觸摸的字段可以使用,然后拿這些字段的數據開始來寫方法。
因為要做的是全局觸摸按鈕,我需要拿到的是按鈕時時的坐標位置,通過改變樣式來改變按鈕任意移動位置。所以就那了changedTouches里面的值。touches里面放的是touchstart的開始位置。用react的時候,touch事件會和click起沖突,導致鏈接點擊有bug,所以需要那touchstart的值在touchend的時候做判斷是點擊事件還是觸摸事件,來控制超鏈接的跳轉。但是在普通h5中並沒有這個問題,下面再細說。
這是html代碼
<div class="active" id="active"> <a class="vote" id="vote" href="https://www.baidu.com"></a> //是全局可以觸摸的對象,點擊又可以做跳轉 </div>
css樣式
*{ margin: 0; padding:0; } //demo里面我就直接去掉所有默認的樣式 .active { width: 100%; height: 100%; background: #9ff5ee; } .vote { position: fixed; bottom:40px; //全局觸摸按鈕使用定位的樣式,可是隨時改變定位的坐標來控制任意移動 right: 15px; width: 60px; height: 60px; border-radius: 50%; background: yellow; }
接下來是重點的javascript代碼,順便講解一下(preventDefault()、stopPropagation()這些方法,ps其實是一邊寫博客一邊學習,復習)
window.onload=function(){ //當一個Web頁面加載完成后就會觸發執行window.onload 里的代碼 var vote=document.getElementById("vote");//通過id取按鈕這個節點 function handleTouchEvent(e) { //只跟蹤一次觸摸 var width=document.documentElement.clientWidth,height=document.documentElement.clientHeight; //這里取屏幕的可視寬度和可視高度來控制按鈕的最大最小訪問 switch (e.type) { case "touchstart": //觸摸開始調用的方法 console.log('start'); break; case "touchend": console.log('end'); //觸摸結束調用的方法,這里不用用到這兩個方法,所以不做詳情介紹 break; case "touchmove": //全局觸摸主要用到touchmove這個方法,touchmove過程中坐標會不斷變化,取這個變化的坐標來控制當前那全局按鈕#vote的坐標。使用style對象來修改目前的樣式,其間做了一個控制,不讓按鈕可以移動上左下右任意方向的屏幕外我用了兩層三目運算符來限制坐標的最小和最大值。 document.getElementById("vote").style.left=parseInt(e.changedTouches[0].clientX) - 60 <= 0 || parseInt(e.changedTouches[0].clientX) >= width - 60 ? (parseInt(e.changedTouches[0].clientX) - 60 <= 0 ? 0 : width - 60) : parseInt(e.changedTouches[0].clientX); //左右方向,最小位置不小於按鈕的直徑,最大不超過屏幕的可視寬度減按鈕直徑,上下方向也是 document.getElementById("vote").style.top=parseInt(e.changedTouches[0].clientY) - 60 <= 0 || parseInt(e.changedTouches[0].clientY) >= height - 60 ? (parseInt(e.changedTouches[0].clientY) - 60 <= 0 ? 0 : height-60) : parseInt(e.changedTouches[0].clientY); break; } } function clickA(e){ e.preventDefault();//這里是點擊事件,阻止了默認事件的觸發,所以超鏈接不會跳轉,沒阻止的話超鏈接就會正常跳轉了 } vote.addEventListener("touchstart", handleTouchEvent, false); vote.addEventListener("touchmove", handleTouchEvent, false); vote.addEventListener("touchend", handleTouchEvent, false); vote.addEventListener("click", clickA, false); }
事件調用的方法有 attachEvent方法適用於IE addEventListener方法適用於FF
attachEvent只有兩個參數,第一個參數為事件名稱,第二個參數為接收事件處理的函數,因為是做h5的觸摸,不需要做ie的兼容,所以沒做全兼容。如果要做全兼容,如下
//兼容所有瀏覽器的事件監聽方法 function(element, type, handler){ if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }
上文demo中事件的調用采用了addEventListener這個方法,這個方法有三個參數,第一個參數表示事件名稱(不含 on,如 "click");第二個參數表示要接收事件處理的函數;第三個參數是一個bool值,一般為false。第三個參數的作用設置事件為冒泡還是捕獲,如果為false那就是冒泡bubbling,為true就是捕獲capture,冒泡和捕獲是什么呢,看圖
兩層div元素,而且都設定有click事件,一般來說,如果我在內層黃色的元素上click不只會觸發黃色元素的click事件,還會同時觸發紅色元素的click事件,由內到外的觸發就是冒泡bubbling,由外層div到內層div觸發就是捕獲。這時候如果想阻止這些冒泡和捕獲事件的發生,就需要用到stopPropagation()方法了,IE中則使用e.cancelBubble = true阻止冒泡,該方法阻止目標元素的冒泡事件,但是會不阻止默認行為。瀏覽器的默認行為就需要用到preventDefault()方法來阻止默認事件,比如上文的阻止超鏈接的跳轉。
阻止冒泡的效果如下
function clickA(e){ alert('aaaa') e.stopPropagation(); //點擊a#vote按鈕,如果沒有加入該行,點擊該事件會同時出發a#vote和div#active上面的事件,加入該行就可以阻止冒泡 e.preventDefault();//這里是點擊事情,阻止了默認事件的觸發,所以超鏈接不會跳轉,沒阻止的話超鏈接就會正常跳轉了 } function clickDiv(e){ alert('444') } document.getElementById("vote").addEventListener("click", clickA, false); //事件的調用 document.getElementById("active").addEventListener("click", clickDiv, false);
以上為原生javascript的實現方法
2.react 實現全局觸摸按鈕(使用react+ES6語法,使用webpack打包工具)
react大體包含(組件 ,JSX, 虛擬DOM, 單向數據流);
JSX也就是HTML直接嵌套到JS代碼里面,所以以下就沒有所謂的html和js分開了。
我們首先還是來觀察react+ES6的事件輸出,根據前面原生的js我們知道移動端的觸摸主要用了touches和changedTouches里面的值,用touches來取得初始的坐標位置,用changedTouches可以取得觸摸后移動位置的坐標。除了這兩個還有一個targetTouches保存着當前坐標。它們都屬於TouchList
對象
TouchList
由Touch
對象構成的數組,通過event.touches
取到。一個Touch
對象代表一個觸點,當有多個手指觸摸屏幕時,TouchList
就會存儲多個Touch
對象,前面說到的identifier
就用來區分每個手指對應的Touch
對象。
touches / changedTouches / targetTouches
touches
一個TouchList對象,包含當前所有接觸屏幕的觸點的Touch對象,不論 touchstart 事件從哪個elment上觸發。
targetTouches
也是一個TouchList對象,包含了如下觸點的 Touch 對象:touchstart從當前事件的目標element上觸發
changedTouches
也是一個 TouchList 對象,對於 touchstart 事件, 這個 TouchList 對象列出在此次事件中新增加的觸點。對於 touchmove 事件,列出和上一次事件相比較,發生了變化的觸點。對於 touchend ,列出離開觸摸平面的觸點(這些觸點對應已經不接觸觸摸平面的手指)。
touchend這里要特別注意,touches和targetTouches只存儲接觸屏幕的觸點,要獲取觸點最后離開的狀態要使用changedTouches ,但是在react單向數據流中,可以實時改變狀態機state中的值,所以可以不斷取得接觸屏幕觸點的位置來改變觸摸對象的坐標,所以react里面可用touches和targetTouches在touchend和touchmove中
e.touches、e.targetTouches、e.changedTouches 的輸出都如下圖,都是Touch對象
react綁定事件處理函數---->觸摸(React 標准化了事件對象,因此在不同的瀏覽器中都會有相同的屬性。)
onTouchCancel
onTouchEnd
onTouchMove
onTouchStart
觸摸只會在移動設備上產生。在使用觸摸時,嘗試着用onclick來處理同個元素上的點擊事件,發現點擊事件根本沒反應。所以我就在onTouchEnd處理元素觸摸后的結果。當元素觸摸結束后位置沒偏移,就可以當成是點擊事件,處理點擊事件要完成的任務。
接下來上代碼。
'use strict'; import React from 'react'; import './index.less'; //這里用的都是es6的引入方式 export default class Back extends React.Component { static defaultProps = { prefixCls: 'zm' }; constructor() { //初始化state的工作要在constructor中完成。 super(); //在react中,無法獲取dom節點,把要改變的值放在state狀態機中 this.state = { top: '76%', //觸摸按鈕初始位置(距離左邊頂部的位置) left: '83%', startX: '', //觸摸按鈕初始的坐標 startY: '', x: 0, //記錄觸摸按鈕觸摸后有無變化 y: 0, height: document.documentElement.clientHeight, //屏幕可視寬高 width: document.documentElement.clientWidth, }; } handleStart(e){ e.preventDefault(); //前面原生js用的是touches,其實還有一個targetTouches,在這兩個的輸出結果是一致的。 this.setState({ //當觸摸開始時候,記錄當時的坐標值,還有設置觸摸變化的xy軸的變化為0,因為當新一輪觸摸開始時候,必須重新設置,相當於初始化 startX : e.targetTouches[0].clientX, startY : e.targetTouches[0].clientY, x:0, y:0, }); } handleTouchMove(e) { const { startX, startY, width, height } = this.state;//取得初始坐標和屏幕可視寬高 this.setState({ //設置當前的坐標位置,思路和上面原生的一樣,不過由於react有實時變化的狀態機state,所以在此用touches,targetTouches //都可以來設置實時變化的值,不用用到changedTouches; left: parseInt(e.touches[0].clientX) - 48 <= 0 || parseInt(e.touches[0].clientX) >= width - 48 ? (parseInt(e.touches[0].clientX) - 48 <= 0 ? 0 : width - 48) : parseInt(e.touches[0].clientX), top: parseInt(e.touches[0].clientY) - 48 <= 0 || parseInt(e.touches[0].clientY) >= height - 48 ? (parseInt(e.touches[0].clientY) - 48 <= 0 ? 0 : height - 48) : parseInt(e.touches[0].clientY), x: e.touches[0].clientX - startX, //當前觸摸點-初始坐標取得實時變化值 y: e.touches[0].clientY - startY, }); } handleTouchEnd (e) { const { x, y } = this.state; if (x == 0 && y == 0) { //觸摸結束后,判斷實時變化值有沒變化,沒變化則視為點擊事件。 window.location.href = '#'; }; } render() { const { prefixCls } = this.props; const { top, left, height, width } = this.state; //取得實時狀態機state的值 return ( <div className="zm" style={{top:`${top}`,left:`${left}` }} //取得state的值實時改變觸摸點的坐標位置 onTouchStart={this.handleStart.bind(this)} //使用bind(this)改變函數作用域,不加上bind則this指向的是全局對象window而報錯。 onTouchMove={this.handleTouchMove.bind(this)} onTouchEnd={this.handleTouchEnd.bind(this)} /> ) } }
react中要注意的是使用touch時 click沒反應,需要在touchend中做判斷做處理