成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

HTML5中手勢(shì)原理分析與數(shù)學(xué)知識(shí)的實(shí)踐

rollback / 1165人閱讀

摘要:中手勢(shì)原理分析與數(shù)學(xué)知識(shí)的實(shí)踐引言在這觸控屏的時(shí)代,人性化的手勢(shì)操作已經(jīng)深入了我們生活的每個(gè)部分。這篇博文主要是解析了移動(dòng)端常用手勢(shì)的原理,及從前端的角度學(xué)習(xí)過(guò)程中所使用的數(shù)學(xué)知識(shí)。

HTML5中手勢(shì)原理分析與數(shù)學(xué)知識(shí)的實(shí)踐 引言

在這觸控屏的時(shí)代,人性化的手勢(shì)操作已經(jīng)深入了我們生活的每個(gè)部分?,F(xiàn)代應(yīng)用越來(lái)越重視與用戶的交互及體驗(yàn),手勢(shì)是最直接且最為有效的交互方式,一個(gè)好的手勢(shì)交互,能降低用戶的使用成本和流程,大大提高了用戶的體驗(yàn)。

近期,公司的多個(gè)項(xiàng)目中都對(duì)手勢(shì)有著較高的需求,已有的手勢(shì)庫(kù)無(wú)法完全cover,因此便擼了一個(gè)輕量、便于使用的移動(dòng)端手勢(shì)庫(kù)。這篇博文主要是解析了移動(dòng)端常用手勢(shì)的原理,及從前端的角度學(xué)習(xí)過(guò)程中所使用的數(shù)學(xué)知識(shí)。希望能對(duì)大家有一點(diǎn)點(diǎn)的啟發(fā)作用,也期待大神們指出不足甚至錯(cuò)誤,感恩。

主要講解項(xiàng)目中經(jīng)常使用到的五種手勢(shì):

拖動(dòng): drag

雙指縮放: pinch

雙指旋轉(zhuǎn): rotate

單指縮放: singlePinch

單指旋轉(zhuǎn): singleRotate

Tips :
因?yàn)?tapswipe 很多基礎(chǔ)庫(kù)中包含,為了輕便,因此并沒(méi)有包含,但如果需要,可進(jìn)行擴(kuò)展;
實(shí)現(xiàn)原理

眾所周知,所有的手勢(shì)都是基于瀏覽器原生事件touchstart, touchmove, touchend, touchcancel進(jìn)行的上層封裝,因此封裝的思路是通過(guò)一個(gè)個(gè)相互獨(dú)立的事件回調(diào)倉(cāng)庫(kù)handleBus,然后在原生touch事件中符合條件的時(shí)機(jī)觸發(fā)并傳出計(jì)算后的參數(shù)值,完成手勢(shì)的操作。實(shí)現(xiàn)原理較為簡(jiǎn)單清晰,先不急,我們先來(lái)理清一些使用到的數(shù)學(xué)概念并結(jié)合代碼,將數(shù)學(xué)運(yùn)用到實(shí)際問(wèn)題中,數(shù)學(xué)部分可能會(huì)比較枯燥,但希望大家堅(jiān)持讀完,相信會(huì)收益良多。

基礎(chǔ)數(shù)學(xué)知識(shí)函數(shù)

我們常見(jiàn)的坐標(biāo)系屬于線性空間,或稱向量空間(Vector Space)。這個(gè)空間是一個(gè)由點(diǎn)(Point) 和 向量(Vector) 所組成集合;

點(diǎn)(Point)

可以理解為我們的坐標(biāo)點(diǎn),例如原點(diǎn)O(0,0),A(-1,2),通過(guò)原生事件對(duì)象的touches可以獲取觸摸點(diǎn)的坐標(biāo),參數(shù)index代表第幾接觸點(diǎn);

向量(Vector)

是坐標(biāo)系中一種 既有大小也有方向的線段,例如由原點(diǎn)O(0,0)指向點(diǎn)A(1,1)的箭頭線段,稱為向量a,則a=(1-0,1-0)=(1,1);

如下圖所示,其中ij向量稱為該坐標(biāo)系的單位向量,也稱為基向量,我們常見(jiàn)的坐標(biāo)系單位為1,即i=(1,0);j=(0,1);

獲取向量的函數(shù):

向量模

代表 向量的長(zhǎng)度,記為|a|,是一個(gè)標(biāo)量,只有大小,沒(méi)有方向;

幾何意義代表的是以x,y為直角邊的直角三角形的斜邊,通過(guò)勾股定理進(jìn)行計(jì)算;

getLength函數(shù):

向量的數(shù)量積

向量同樣也具有可以運(yùn)算的屬性,它可以進(jìn)行加、減、乘、數(shù)量積和向量積等運(yùn)算,接下來(lái)就介紹下我們使用到的數(shù)量積這個(gè)概念,也稱為點(diǎn)積,被定義為公式:

當(dāng)a=(x1,y1),b=(x2,y2),則a·b=|a|·|b|·cosθ=x1·x2+y1·y2;
共線定理

共線,即兩個(gè)向量處于 平行 的狀態(tài),當(dāng)a=(x1,y1),b=(x2,y2),則存在唯一的一個(gè)實(shí)數(shù)λ,使得a=λb,代入坐標(biāo)點(diǎn)后,可以得到 x1·y2= y1·x2;

因此當(dāng)x1·y2-x2·y1>0 時(shí),既斜率 ka > kb ,所以此時(shí)b向量相對(duì)于a向量是屬于順時(shí)針旋轉(zhuǎn),反之,則為逆時(shí)針;

旋轉(zhuǎn)角度

通過(guò)數(shù)量積公式我們可以推到求出兩個(gè)向量的夾角:

cosθ=(x1·x2+y1·y2)/(|a|·|b|);

然后通過(guò)共線定理我們可以判斷出旋轉(zhuǎn)的方向,函數(shù)定義為:

矩陣與變換

由于空間最本質(zhì)的特征就是其可以容納運(yùn)動(dòng),因此在線性空間中,

我們用向量來(lái)刻畫(huà)對(duì)象,而矩陣便是用來(lái)描述對(duì)象的運(yùn)動(dòng);
而矩陣是如何描述運(yùn)動(dòng)的呢?

我們知道,通過(guò)一個(gè)坐標(biāo)系基向量便可以確定一個(gè)向量,例如 a=(-1,2),我們通常約定的基向量是 i = (1,0) 與 j = (0,1); 因此:

a = -1i + 2j = -1(1,0) + 2(0,1) = (-1+0,0+2) = (-1,2);

而矩陣變換的,其實(shí)便是通過(guò)矩陣轉(zhuǎn)換了基向量,從而完成了向量的變換;

例如上面的栗子,把a向量通過(guò)矩陣(1,2,3,0)進(jìn)行變換,此時(shí)基向量i(1,0)變換成(1,-2)j(0,1)變換成(3,0),沿用上面的推導(dǎo),則

a = -1i + 2j = -1(1,-2) + 2(3,0) = (5,2);

如下圖所示:
A圖表示變換之前的坐標(biāo)系,此時(shí)a=(-1,2),通過(guò)矩陣變換后,基向量i,j的變換引起了坐標(biāo)系的變換,變成了下圖B,因此a向量由(-1,2)變換成了(5,2);

其實(shí)向量與坐標(biāo)系的關(guān)聯(lián)不變(a = -1i+2j),是基向量引起坐標(biāo)系變化,然后坐標(biāo)系沿用關(guān)聯(lián)導(dǎo)致了向量的變化;

結(jié)合代碼

其實(shí)CSS的transform等變換便是通過(guò)矩陣進(jìn)行的,我們平時(shí)所寫(xiě)的translate/rotate等語(yǔ)法類似于一種封裝好的語(yǔ)法糖,便于快捷使用,而在底層都會(huì)被轉(zhuǎn)換成矩陣的形式。例如transform:translate(-30px,-30px)編譯后會(huì)被轉(zhuǎn)換成transform : matrix(1,0,0,1,30,30);

通常在二維坐標(biāo)系中,只需要 2X2 的矩陣便足以描述所有的變換了, 但由于CSS是處于3D環(huán)境中的,因此CSS中使用的是 3X3 的矩陣,表示為:

其中第三行的0,0,1代表的就是z軸的默認(rèn)參數(shù)。這個(gè)矩陣中,(a,b) 即為坐標(biāo)軸的 i基,而(c,d)既為j基,ex軸的偏移量,fy軸的偏移量;因此上栗便很好理解,translate并沒(méi)有導(dǎo)致i,j基改變,只是發(fā)生了偏移,因此translate(-30px,-30px) ==> matrix(1,0,0,1,30,30)~

所有的transform語(yǔ)句,都會(huì)發(fā)生對(duì)應(yīng)的轉(zhuǎn)換,如下:

// 發(fā)生偏移,但基向量不變;
transform:translate(x,y) ==> transform:matrix(1,0,0,1,x,y)

// 基向量旋轉(zhuǎn);
transform:rotate(θdeg)==> transform:matrix(cos(θ·π/180),sin(θ·π/180),-sin(θ·π/180),cos(θ·π/180),0,0)

// 基向量放大且方向不變;
transform:scale(s) ==> transform:matrix(s,0,0,s,0,0)

translate/rotate/scale等語(yǔ)法十分強(qiáng)大,讓我們的代碼更為可讀且方便書(shū)寫(xiě),但是matrix有著更強(qiáng)大的轉(zhuǎn)換特性,通過(guò)matrix,可以發(fā)生任何方式的變換,例如我們常見(jiàn)的鏡像對(duì)稱,transform:matrix(-1,0,0,1,0,0);

MatrixTo

然而matrix雖然強(qiáng)大,但可讀性卻不好,而且我們的寫(xiě)入是通過(guò)translate/rotate/scale的屬性,然而通過(guò)getComputedStyle讀取到的 transform卻是matrix:

transform:matrix(1.41421, 1.41421, -1.41421, 1.41421, -50, -50);

請(qǐng)問(wèn)這個(gè)元素發(fā)生了怎么樣的變化?。。這就一臉懵逼了。-_-|||

因此,我們必須要有個(gè)方法,來(lái)將matrix翻譯成我們更為熟悉的translate/rotate/scale方式,在理解了其原理后,我們便可以著手開(kāi)始表演咯~

我們知道,前4個(gè)參數(shù)會(huì)同時(shí)受到rotatescale的影響,具有兩個(gè)變量,因此需要通過(guò)前兩個(gè)參數(shù)根據(jù)上面的轉(zhuǎn)換方式列出兩個(gè)不等式:

cos(θ·π/180)*s=1.41421;

sin(θ·π/180)*s=1.41421;

將兩個(gè)不等式相除,即可以輕松求出θs了,perfect?。『瘮?shù)如下:

手勢(shì)原理

接下來(lái)我們將上面的函數(shù)用到實(shí)際環(huán)境中,通過(guò)圖示的方式來(lái)模擬手勢(shì)的操作,簡(jiǎn)要地講解手勢(shì)計(jì)算的原理。希望各位大神理解這些基礎(chǔ)的原理后,能創(chuàng)造出更多炫酷的手勢(shì),像我們?cè)?b>mac觸控板上使用的一樣。

下面圖例:

圓點(diǎn): 代表手指的觸碰點(diǎn);

兩個(gè)圓點(diǎn)之間的虛線段: 代表雙指操作時(shí)組成的向量;

a向量/A點(diǎn):代表在 touchstart 時(shí)獲取的初始向量/初始點(diǎn);

b向量/B點(diǎn):代表在 touchmove 時(shí)獲取的實(shí)時(shí)向量/實(shí)時(shí)點(diǎn);

坐標(biāo)軸底部的公式代表需要計(jì)算的值;

Drag(拖動(dòng)事件)

上圖是模擬了拖動(dòng)手勢(shì),由A點(diǎn)移動(dòng)到B點(diǎn),我們要計(jì)算的便是這個(gè)過(guò)程的偏移量;

因此我們?cè)?b>touchstart中記錄初始點(diǎn)A的坐標(biāo):

// 獲取初始點(diǎn)A;
let startPoint = getPoint(ev,0);

然后在touchmove事件中獲取當(dāng)前點(diǎn)并實(shí)時(shí)的計(jì)算出△x△y

// 實(shí)時(shí)獲取初始點(diǎn)B;
let curPoint = getPoint(ev,0);

// 通過(guò)A、B兩點(diǎn),實(shí)時(shí)的計(jì)算出位移增量,觸發(fā) drag 事件并傳出參數(shù);
_eventFire("drag", {
    delta: {
        deltaX: curPoint.x - startPoint.x,
        deltaY: curPoint.y - startPoint.y,
    },
    origin: ev,
});
Tips: fire函數(shù)即遍歷執(zhí)行drag事件對(duì)應(yīng)的回調(diào)倉(cāng)庫(kù)即可;
Pinch(雙指縮放)

上圖是雙指縮放的模擬圖,雙指由a向量放大到b向量,通過(guò)初始狀態(tài)時(shí)的a向量的模與touchmove中獲取的b向量的模進(jìn)行計(jì)算,便可得出縮放值:

// touchstart中計(jì)算初始雙指的向量模;
let vector1 = getVector(secondPoint, startPoint);
let pinchStartLength = getLength(vector1);

// touchmove中計(jì)算實(shí)時(shí)的雙指向量模;
let vector2 = getVector(curSecPoint, curPoint);
let pinchLength = getLength(vector2);
this._eventFire("pinch", {
    delta: {
        scale: pinchLength / pinchStartLength,
    },
    origin: ev,
});
Rotate(雙指旋轉(zhuǎn))

初始時(shí)雙指向量a,旋轉(zhuǎn)到b向量,θ便是我們需要的值,因此只要通過(guò)我們上面構(gòu)建的getAngle函數(shù),便可求出旋轉(zhuǎn)的角度:

// a向量;
let vector1 = getVector(secondPoint, startPoint);

// b向量;
let vector2 = getVector(curSecPoint, curPoint);

// 觸發(fā)事件;
this._eventFire("rotate", {
    delta: {
        rotate: getAngle(vector1, vector2),
    },
    origin: ev,
});
singlePinch(單指縮放)

與上面的手勢(shì)不同,單指縮放和單指旋轉(zhuǎn)都需要多個(gè)特有概念:

操作元素(operator):需要操作的元素。上面三個(gè)手勢(shì)其實(shí)并不關(guān)心操作元素,因?yàn)閱渭兛渴謩?shì)自身,便能計(jì)算得出正確的參數(shù)值,而單指縮放和旋轉(zhuǎn)需要依賴于操作元素的基準(zhǔn)點(diǎn)(操作元素的中心點(diǎn))進(jìn)行計(jì)算;

按鈕:因?yàn)閱沃傅氖謩?shì)與拖動(dòng)(drag)手勢(shì)是相互沖突的,需要一種特殊的交互方式來(lái)進(jìn)行區(qū)分,這里是通過(guò)特定的區(qū)域來(lái)區(qū)分,類似于一個(gè)按鈕,當(dāng)在按鈕上操作時(shí),是單指縮放或者旋轉(zhuǎn),而在按鈕區(qū)域外,則是常規(guī)的拖動(dòng),實(shí)踐證明,這是一個(gè)用戶很容易接受且體驗(yàn)較好的操作方式;

圖中由a向量單指放大到b向量,對(duì)操作元(正方形)素進(jìn)行了中心放大,此時(shí)縮放值即為b向量的模 / a向量的模;

// 計(jì)算單指操作時(shí)的基準(zhǔn)點(diǎn),獲取operator的中心點(diǎn);
let singleBasePoint = getBasePoint(operator);

// touchstart 中計(jì)算初始向量模;
let pinchV1 = getVector(startPoint,singleBasePoint);
singlePinchStartLength = getLength(pinchV1);

// touchmove 中計(jì)算實(shí)時(shí)向量模;
pinchV2 = getVector(curPoint, singleBasePoint);
singlePinchLength = getLength(pinchV2);

// 觸發(fā)事件;
this._eventFire("singlePinch", {
    delta: {
        scale: singlePinchLength / singlePinchStartLength,
    },
    origin: ev,
});
singleRotate(單指旋轉(zhuǎn))

結(jié)合單指縮放和雙指旋轉(zhuǎn),可以很簡(jiǎn)單的知道 θ便是我們需要的旋轉(zhuǎn)角度;

// 獲取初始向量與實(shí)時(shí)向量
let rotateV1 = getVector(startPoint, singleBasePoint);
let rotateV2 = getVector(curPoint, singleBasePoint);

// 通過(guò) getAngle 獲取旋轉(zhuǎn)角度并觸發(fā)事件;
this._eventFire("singleRotate", {
    delta: {
        rotate: getAngle(rotateV1, rotateV2),
    },
    origin: ev,
});
運(yùn)動(dòng)增量

由于touchmove事件是個(gè)高頻率的實(shí)時(shí)觸發(fā)事件,一個(gè)拖動(dòng)操作,其實(shí)觸發(fā)了N次的touchmove事件,因此計(jì)算出來(lái)的值只是一種增量,即代表的是一次 touchmove事件增加的值,只代表一段很小的值,并不是最終的結(jié)果值,因此需要由mtouch.js外部維護(hù)一個(gè)位置數(shù)據(jù),類似于:

//    真實(shí)位置數(shù)據(jù);
let dragTrans = {x = 0,y = 0};

// 累加上 mtouch 所傳遞出的增量 deltaX 與 deltaY;
dragTrans.x += ev.delta.deltaX;
dragTrans.y += ev.delta.deltaY;

// 通過(guò) transform 直接操作元素;
set($drag,dragTrans);
初始位置

維護(hù)外部的這個(gè)位置數(shù)據(jù),如果初始值像上述那樣直接取0,則遇到使用css設(shè)置了transform屬性的元素便無(wú)法正確識(shí)別了,會(huì)導(dǎo)致操作元素開(kāi)始時(shí)瞬間跳回(0,0)的點(diǎn),因此我們需要初始去獲取一個(gè)元素真實(shí)的位置值,再進(jìn)行維護(hù)與操作。此時(shí),便需要用到上面我們提到的getComputedStyle方法與matrixTo函數(shù):

// 獲取css transform屬性,此時(shí)得到的是一個(gè)矩陣數(shù)據(jù);
// transform:matrix(1.41421,1.41421,-1.41421,1.41421,-50,-50);
let style = window.getComputedStyle(el,null);
let cssTrans = style.transform || style.webkitTransform;

// 按規(guī)則進(jìn)行轉(zhuǎn)換,得到:
let initTrans = _.matrixTo(cssTrans);

// {x:-50,y:-50,scale:2,rotate:45};
// 即該元素設(shè)置了:transform:translate(-50px,-50px) scale(2) rotate(45deg);
結(jié)語(yǔ)

至此,相信大家對(duì)手勢(shì)的原理已經(jīng)有基礎(chǔ)的了解,基于這些原理,我們可以再封裝出更多的手勢(shì),例如雙擊,長(zhǎng)按,掃動(dòng),甚至更酷炫的三指、四指操作等,讓?xiě)?yīng)用擁有更多人性化的特質(zhì)。

基于以上原理,我封裝了幾個(gè)常見(jiàn)的工具:(求star -.-)

Tips: 因?yàn)橹会槍?duì)移動(dòng)端,需在移動(dòng)設(shè)備中打開(kāi)demo,或者pc端開(kāi)啟mobile調(diào)試模式!

mtouch.js : 移動(dòng)端的手勢(shì)庫(kù),封裝了上述的五種手勢(shì),精簡(jiǎn)的api設(shè)計(jì),涵蓋了常見(jiàn)的手勢(shì)交互,基于此也可以很方便的進(jìn)行擴(kuò)展。

demo
github

touchkit.js : 基于mtouch所封裝的一層更貼近業(yè)務(wù)的工具包,可用于制作多種手勢(shì)操作業(yè)務(wù),一鍵開(kāi)啟,一站式服務(wù)。

demo
github

mcanvas.js : 基于canvas 開(kāi)放極簡(jiǎn)的api實(shí)現(xiàn)圖片<段落文字> <混排文字> <裁剪> <平移> <旋轉(zhuǎn)> <縮放> <水印添加> 一鍵導(dǎo)出等。

demo
github

致謝

張?chǎng)涡瘢?獲取元素CSS值之getComputedStyle方法熟悉

張?chǎng)涡瘢豪斫釩SS3 transform中的Matrix(矩陣)

AlloyTeam團(tuán)隊(duì)的AlloyFinger

hcysunyangd: 從矩陣與空間操作的關(guān)系理解CSS3的transform

線性代數(shù)的理解 學(xué)完再看覺(jué)得自己弱爆了

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/84675.html

相關(guān)文章

  • 優(yōu)秀博文收藏(不定期更新)

    摘要:我的書(shū)簽我的書(shū)簽謹(jǐn)慎導(dǎo)入,小心覆蓋工具類版本管理快速切換源配置教程指南可視化工具前端工具集前端助手網(wǎng)絡(luò)封包截取工具格式化工具標(biāo)注工具模擬請(qǐng)求類深入淺出布局你所不知道的動(dòng)畫(huà)技巧與細(xì)節(jié)常用代碼黑魔法小技巧,讓你少寫(xiě)不必要的,代碼更優(yōu)雅一勞永 我的書(shū)簽 我的書(shū)簽(謹(jǐn)慎導(dǎo)入,小心覆蓋) 工具類 nvm: node版本管理 nrm: 快速切換npm源 shell: zsh+on-my-zsh配...

    sunsmell 評(píng)論0 收藏0
  • 優(yōu)秀博文收藏(不定期更新)

    摘要:我的書(shū)簽我的書(shū)簽謹(jǐn)慎導(dǎo)入,小心覆蓋工具類版本管理快速切換源配置教程指南可視化工具前端工具集前端助手網(wǎng)絡(luò)封包截取工具格式化工具標(biāo)注工具模擬請(qǐng)求類深入淺出布局你所不知道的動(dòng)畫(huà)技巧與細(xì)節(jié)常用代碼黑魔法小技巧,讓你少寫(xiě)不必要的,代碼更優(yōu)雅一勞永 我的書(shū)簽 我的書(shū)簽(謹(jǐn)慎導(dǎo)入,小心覆蓋) 工具類 nvm: node版本管理 nrm: 快速切換npm源 shell: zsh+on-my-zsh配...

    zhangfaliang 評(píng)論0 收藏0
  • 2017-08-05 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選系列深度剖析簡(jiǎn)介與截圖等應(yīng)用譯所有你需要知道的關(guān)于完全理解事件循環(huán)及其度量新特性總結(jié)用信號(hào)來(lái)控制異步流程中文的使用眾成翻譯拖放和詳解與實(shí)例風(fēng)雨過(guò)后見(jiàn)彩虹中手勢(shì)原理分析與數(shù)學(xué)知識(shí)的實(shí)踐個(gè)人文章中的興趣部落的前端性 2017-08-05 前端日?qǐng)?bào) 精選 WebVR系列——深度剖析SVG 簡(jiǎn)介與截圖等應(yīng)用[譯] 所有你需要知道的關(guān)于完全理解 Node.js 事件循環(huán)及其度量HTM...

    lufficc 評(píng)論0 收藏0
  • 全棧是概念,興趣亦為追求(全棧開(kāi)發(fā)者)

    摘要:耐得住寂寞,才能等得到花開(kāi)慢慢積累自己的知識(shí),不斷疊加,全面優(yōu)化,無(wú)論在哪個(gè)領(lǐng)域都可以有你的一席之地,即為有志者事竟成,破釜沉舟,百二秦關(guān)終屬楚也祝我們能向未來(lái)發(fā)展的開(kāi)發(fā)者們苦心人天不負(fù),臥薪嘗膽,三千越甲可吞吳。 我們今天來(lái)了聊一聊一個(gè)話題——全棧開(kāi)發(fā) 作為一個(gè)程序員,不管是Java還是C...

    lbool 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<