如何在touch事件中模擬mouse事件中的offsetX offsetY?

1. 原由

最近想製做一個在散開的紙面畫畫的效果,紙面會按必定角度傾斜,就像下面這樣↓ css

canvas
也就是給canvas元素加了 transform: roate(-2deg)

2. 電腦端實現

在電腦端很容易實現,鼠標事件中有offsetX Y,可以得到鼠標位置相對於目標節點的位置,能夠簡單理解爲相對於元素左上角的座標。得到座標後,再按座標繪製到Canvas上,一切就OK了,不過到了移動端就有點麻煩了。css3

3. 移動端實現

移動端touch事件有如下幾個屬性git

  • ClientX Y 相對於視口的座標
  • pageX Y 相對於頁面左上角原點的座標
  • screenX Y 相對於屏幕的座標標
  • movementX Y 相對於上一次座標的座標

然而就是沒有offsetX Y,怎麼辦,本身模擬試試?github

3.1 第一次模擬

  1. 得到pageX Y
  2. 經過offsetTop left計算元素左上角的頂點位置vertex
  3. 計算相對座標

代碼以下:canvas

function getVertexPosition(el) {
    let currentTarget = el
    let top = 0
    let left = 0
    while (currentTarget !== null) {
        top += currentTarget.offsetTop
        left += currentTarget.offsetLeft
        currentTarget = currentTarget.offsetParent
    }
    return { top, left }
}

let vertex = getVertexPosition(canvas)
canvas.addEventListener('touchmove',(e)=>{
    let offsetX = e.pageX-vertex.left
    let offsetY = e.pageX-vertex.top
})
複製代碼

在頁面沒有設置任何transform屬性的狀況下,這個代碼是生效的,能得到正確的座標點。 然而若是父元素或目標元素有任何transform屬性,座標就會錯誤,好比本文中的canvas元素,設置了transform: roate(-2deg),座標就發生了偏移。 隨後搜索了一下是否有相似的解決方案,結果都是這樣的代碼,都不能對transform元素生效。緩存

3.2 偏移緣由

偏移緣由是由於變換後,元素的座標軸已經改變了,而咱們獲得的座標仍是變換前的座標,天然而然就發生錯誤了。 bash

座標圖
如圖所示,實際觸摸點是綠色,本應也繪製在綠色座標點,可是咱們如今的座標系仍然是以前的座標系,就致使了繪製點相對於原來的座標點繪製,也即紫色,致使繪製偏移。 解決辦法也很簡單,將觸摸點按照新座標系計算獲得值,就是真實的相對座標了。 步驟:

  1. 得到點擊座標(pageX pageY)
  2. 得到旋轉角度 旋轉中心
  3. 將旋轉中心於元素頂點座標相加,獲得整個頁面的旋轉中心
  4. 旋轉相同負角度(逆運算),將點擊座標還原到真實的座標系
  5. 減去頂點座標,得到真實相對座標。

第二次模擬

  1. 首先得到計算後的樣式,得到transform相關屬性
let style = window.getComputedStyle(el)
console.log(style) 
複製代碼

得到的transform屬性以下wordpress

transform: "matrix(0.999391, -0.0348995, 0.0348995, 0.999391, 0, 0)"
transformBox: "view-box"
transformOrigin: "160px 240px"
transformStyle: "flat"
複製代碼

關於transform:matrix以及如何理解查看張鑫旭的文章。 簡而言之,就是transform屬性都是經過transform:matrix進行矩陣計算獲得座標的。性能

  1. 解析transform屬性 因爲這裏涉及到矩陣運算,須要引入math.jsui

    let transform = style.transform
    let transformOrigin = style.transformOrigin
    
    let origin = { x: 0, y: 0 }
    let matrix = math.ones([3, 3])
    if (transform !== 'none') {
        let originArray = transformOrigin.split(' ')
        origin.x = parseInt(originArray[0])
        origin.y = parseInt(originArray[1]) //矩陣的座標變化是基於變換中心得。
    
        let matrixString = transform.match(/\(([^)]*)\)/)[1]
        let stringArray = matrixString.split(',')
        let temp = []
        stringArray.forEach((value) => {
            temp.push(parseFloat(value.trim()))
        })
        temp = [
            [temp[0], temp[2], temp[4]],
            [temp[1], temp[3], temp[5]],
            [0, 0, 1],
        ]
    
        matrix = math.inv(temp) //進行逆矩陣
    } else {
        matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
    }
    複製代碼

    這樣咱們就獲得了原變換矩陣的逆矩陣和變換中心,就能將觸摸點正確的還原到原座標系了

  2. 計算

function computPosition(obj){
    let { matrix, origin, vertex: { top, left },ponit:{x,y} } = obj
    x = x - left - origin.x
    y = y - top - origin.y
    let result = math.multiply(matrix, [x, y, 1])
    x = result[0] + origin.x
    y = result[1] + origin.y
    return (x,y)
}
複製代碼
  1. 實際實現
    1. 因爲子元素會受到父元素變換影響,所以須要遍歷全部父元素
    2. 因爲getComputStyle()和得到頂點座標有較大性能消耗,最好將相關參數緩存起來 實際代碼實現見此github.com/Geylnu/touc…

其它

W3C給出了沒有爲touch事件添加offsetX Y的緣由github.com/w3c/touch-e…

相關文章
相關標籤/搜索