Android系統中 webkit 核心瀏覽器position:fixed所造成的相容問題
背景概述
桌面瀏覽器本身就支援 position: fixed
. 但 mobile Safari在iOS5之前不支援, 我們只能定製一個模擬滾動的工具來替代原生的滾動.
本教程就是在Mobile Web App上如何實現position: fixed的,就以下幾點來講:
- 如何佈局
- 通過transforms實現滾動的動畫效果
- 通過 transitions實現滾動的慣性衝力
- 觸控式螢幕幕時中止滾動,即在打斷 transitions的執行
如何佈局
要達到目的,選好固定部分和滾動部分是個重點. 本例是個豎排的三欄,頂部和底部固定,中間滾動.
<html> <head> <style> .TOP_TOOLBAR, .BOTTOM_TOOLBAR { height: 50px; width: 100%; position: absolute; } .TOP_TOOLBAR { top: 0; } .BOTTOM_TOOLBAR { bottom: 0; } .SCROLLER_FRAME { width: 100%; position: absolute; top: 50px; bottom: 50px; } </style> </head> <body> <div class=”TOP_TOOLBAR”> ... 頂部工具欄 ... </div> <div class=”SCROLLER_FRAME”> <div class=”SCROLLER”> ... 滾動區域 ... </div> </div> <div class=”BOTTOM_TOOLBAR”> ... 底部工具欄 ... </div> </body> </html>
以上程式碼中固定不動的就是class為TOP_TOOLBAR
, SCROLLER_FRAME
, BOTTOM_TOOLBAR的元素
. 只有class為 SCROLLER的元素
. The SCROLLER_FRAME
即是指定了可滾動的範圍元素.下一步我們就來寫SCROLLER實現在
SCROLLER_FRAME內滾動的JS程式碼
.
用動畫驅動滾動
要使滾動部分有動畫效果首先就得把原生的滾動給阻止了.
document.body.addEventListener('touchmove', function(e) { // This prevents native scrolling from happening. e.preventDefault(); }, false);
阻止原生滾動後我們給滾動區追加touch事件然後通過 webkit-transforms來定位到使用者手指拖動的位置. 要實現滾動效果需要儲存一些狀態值, 我們定義一個Scroller類來接收要滾動的元素.
Scroller = function(element) { this.element = this; this.startTouchY = 0; this.animateTo(0); element.addEventListener(‘touchstart’, this, false); element.addEventListener(‘touchmove’, this, false); element.addEventListener(‘touchend’, this, false); } Scroller.prototype.handleEvent = function(e) { switch (e.type) { case “touchstart”: this.onTouchStart(e); break; case “touchmove”: this.onTouchMove(e); break; case “touchend”: this.onTouchEnd(e); break; } } Scroller.prototype.onTouchStart = function(e) { // 這部分的實現第4步會講. this.stopMomentum(); this.startTouchY = e.touches[0].clientY; this.contentStartOffsetY = this.contentOffsetY; } Scroller.prototype.onTouchMove = function(e) { if (this.isDragging()) { var currentY = e.touches[0].clientY; var deltaY = currentY - this.startTouchY; var newY = deltaY + this.contentStartOffsetY; this.animateTo(newY); } } Scroller.prototype.onTouchEnd = function(e) { if (this.isDragging()) { if (this.shouldStartMomentum()) { // 這部分的實現第3步會講. this.doMomentum(); } else { this.snapToBounds(); } } } Scroller.prototype.animateTo = function(offsetY) { this.contentOffsetY = offsetY; // 在 webkit-transforms用 translate3d 的動畫會得到硬體加速,效能顯著提高 this.element.style.webkitTransform = ‘translate3d(0, ‘ + offsetY + ‘px, 0)’; } // 這方法的實現給讀者作個練習吧. // 你需要計算當前內容滾動到相對於可滾動範圍的位置. 比如滾動超出範圍要修正位置. Scroller.prototype.snapToBounds = function() { ... } // 又是個練習. // 就是判斷使用者的touch手勢是不是拖動式的 Scroller.prototype.isDragging = function() { ... } // 再來個練習吧. // 就是滾出範圍時要作用上慣性衝力效果. Scroller.prototype.shouldStartMomentum = function() { ... }
至此我們的元素可以在滾動範圍裡滾動了,接下來就是慣性衝力的實現了. 為了不顯得囉嗦以上幾個簡單的方法由讀者實現. 下一步我們來講慣性衝力的實現.
用動畫驅動滾動
使用者拖動力度和速度達到一定程式時,滾動的內容在手指離開後應當給予相應的慣性衝力效果反饋,使用webkit-transition屬性來實現減速效果. 有3個值需要作用到webkit-transition上:
- 滾動距離
- 動畫持續時間
- transition的運動軌跡演算法
滾動距離和時間可以通過拖動結束的速率和一個加速常量比 ( 0.0005 px/ms^2)計算得出. doMomentum()方法就是設定
transition的.
Scroller.prototype.doMomentum = function() {
// 計算移動距離. 通過 開始位置和時間的比值來實現getEndVelocity方法
var velocity = this.getEndVelocity();
var acceleration = velocity < 0 ? 0.0005 : -0.0005;
var displacement = - (velocity * velocity) / (2 * acceleration);
var time = - velocity / acceleration;
// 設定好 transition執行 transform.當然你要計算出transition的停止時間是必須的否則滾動會超出.
this.element.style.webkitTransition = ‘-webkit-transform ‘ + time +
‘ms cubic-bezier(0.33, 0.66, 0.66, 1)’;
var newY = this.contentOffsetY + displacement;
this.contentOffsetY = newY;
this.element.style.webkitTransform = ‘translate3d(0, ‘ + newY + ‘px, 0)’;
}
這就是慣性衝力的實現! 運轉軌跡演算法是cubic-bezier. 些演算法是模擬自然效果的,調好一個合理的加速值最終看起來就非常的符合現實.還有一點我們沒有提到,就是慣性衝力將滾動內容滾出範圍怎麼處理.這個比較難搞也超出了本教程的範圍. 我們其實是使用幾個 transitions佇列來搞定的:
- 第一個 transition 滾到邊界, 這裡因為終點值一不是0所以我們使用另一個cubic-bezier 演算法.
- 第二個transition滾出邊界一點點, 終點值是0所以 使用原來的 cubic-bezier演算法.
- 第三個 transition減速並將內容帶回邊界內,使用 ease-out演算法效果即可.
要使效果更加真實,第二個和第三個你最好另外調出合適的加速常理比.
觸控式螢幕幕時中止滾動
如果滾動在執行時使用者觸控式螢幕幕內容應當在觸控位置處中止. 這點比較難搞,因為當滾動執行期間我們只知道開始和結束位置沒法知道當前滾動到的確切位置. 下面我們就來講如何計算滾動執行期間的具體位置和在正確的位置中止滾動.
Scroller.prototype.stopMomentum = function() {
if (this.isDecelerating()) {
// 獲取樣式物件.
var style = document.defaultView.getComputedStyle(this.element, null);
// 使用所得樣式值通過webkitCSSMatrix計算出當前transform值.
var transform = new WebKitCSSMatrix(style.webkitTransform);
// 清除transition以免作用到下一個transform.
this.element.style.webkitTransition = ‘’;
// 指定transform到目標位置.
this.animateTo(transform.m42);
}
}
WebKitCSSMatrix
是WebKit核心提供可計算transform的方法. 其他支援HTML5的瀏覽器也有各自的方法.isDecelerating()方法你得自己實現了,作用是儲存一些當前慣性衝力的狀態值
.
總結
通過這個教程你可能實現一個 fixed position的 DOM,基於touch事件實現滾動, 執行和停止慣性衝力. 其他知識點你慢慢消化,當然我們也會將開發都關注的技術慢慢跟進的總結出來.