移動端效果之Swiper

来源:http://www.cnblogs.com/rynxiao/archive/2017/10/09/7640647.html
-Advertisement-
Play Games

寫在前面 最近在做移動端方面運用到了餓了麽的 前端組件庫,因為不想單純用組件而使用它,故想深入瞭解一下實現原理。後續將會繼續研究一下其他的組件實現原理,有興趣的可以關註下。 代碼在這裡: "戳我" 1. 說明 父容器 ,子頁面 2. 核心解析 2.1 頁面初始化 由於所有頁面都在手機屏幕左側一個屏幕 ...


寫在前面

最近在做移動端方面運用到了餓了麽的vue前端組件庫,因為不想單純用組件而使用它,故想深入瞭解一下實現原理。後續將會繼續研究一下其他的組件實現原理,有興趣的可以關註下。

swiper

代碼在這裡:戳我 or github

移動端效果之Picker

1. 說明

父容器overflow:hidden;,子頁面transform:translateX(-100%);width:100%;

2. 核心解析

2.1 頁面初始化

由於所有頁面都在手機屏幕左側一個屏幕寬度的位置,因此最開始的情況是頁面中看不到任何一個子頁面,所以第一步應該設置應該顯示的子頁面,預設情況下defaultIndex:0

function reInitPages() {
    // 得出頁面是否能夠被滑動
    // 1. 子頁面只有一個
    // 2. 用戶手動設置不能滑動 noDragWhenSingle = true
    noDrag = children.length === 1 && noDragWhenSingle;

    var aPages = [];
    var intDefaultIndex = Math.floor(defaultIndex);
    var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length) 
        ? intDefaultIndex : 0;
    
    // 得到當前被激活的子頁面索引
    index = defaultIndex;

    children.forEach(function(child, index) {
        aPages.push(child);
        // 所有頁面移除激活class
        child.classList.remove('is-active');

        if (index === defaultIndex) {
            // 給激活的子頁面加上激活class
            child.classList.add('is-active');
        }
    });

    pages = aPages;
}

2.2 容器滑動開始(onTouchStart)

在低版本的android手機上,設置event.preventDefault()會起到一定的性能提升作用,使得滑動起來不是那麼卡。

前置工作:

  • 如果用戶設置了 prevent:true, 滑動時阻止預設行為
  • 如果用戶設置了stopPropagation:true, 滑動時阻止事件向上傳播
  • 如果動畫尚未結束,阻止滑動
  • 設置dragging:true,滑動開始
  • 設置用戶滾動為false

滑動開始:

使用一個全局對象記錄信息,這些信息包括:

dragState = {
    startTime           // 開始時間
    startLeft           // 開始的X坐標
    startTop            // 開始的Y坐標(相對於整個頁面viewport pageY)
    startTopAbsolute    // 絕對Y坐標(相對於文檔頂部 clientY)
    pageWidth           // 一個頁面寬度
    pageHeight          // 一個頁面的高度
    prevPage            // 上一個頁面
    dragPage            // 當前頁面
    nextPage            // 下一個頁面
};

2.3 容器滑動(onTouchMove)

套用全局dragState,記錄新的信息

dragState = {
    currentLeft         // 開始的X坐標
    currentTop          // 開始的Y坐標(相對於整個頁面viewport pageY)
    currentTopAbsolute  // 絕對Y坐標(相對於文檔頂部 clientY)
};

那麼我們就可以通過開始和滑動中的信息來計算出一些東西:

一、滑動的水平位移(offsetLeft = currentLeft - startLeft

二、滑動的垂直位移(offsetTop = currentTopAbsolute - startTopAbsolute

三、是否是用戶的自然滾動,這裡的自然滾動說的是用戶並不是想滑動swiper,而是想滑動頁面

```javascript
// 條件
// distanceX = Math.abs(offsetLeft);
// distanceY = Math.abs(offsetTop);
distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
```

四、判斷是左移還是右移(offsetLeft < 0 左移,反之,右移)

五、重置位移

```javascript
// 如果存在上一個頁面並且是左移
if (dragState.prevPage && towards === 'prev') {
    // 重置上一個頁面的水平位移為 offsetLeft - dragState.pageWidth
    // 由於 offsetLeft 一直在變化,並且 >0
    // 那麼也就是說 offsetLeft - dragState.pageWidth 的值一直在變大,但是仍未負數
    // 這就是為什麼當連續屬性存在的時候左滑會看到上一個頁面會跟著滑動的原因
    // 這裡的 translate 方法其實很簡單,在滑動的時候去除了動畫效果`transition`,單純改變位移
    // 而在滑動結束的時候,加上`transition`,使得滑動到最後釋放的過渡更加自然
    translate(dragState.prevPage, offsetLeft - dragState.pageWidth);
} 

// 當前頁面跟著滑動
translate(dragState.dragPage, offsetLeft);

// 後一個頁面同理
if (dragState.nextPage && towards === 'next') {
    translate(dragState.nextPage, offsetLeft + dragState.pageWidth);
}
```

2.4 滑動結束(onTouchEnd)

前置工作:

在滑動中,我們是可以實時地來判斷到底是不是用戶的自然滾動userScrolling,如果是用戶自然滾動,那麼swiper的滑動信息就不算數,因此要做一些清除操作:

dragging = false;
dragState = {};

當然如果userScrolling:false,那麼就是滑動子頁面,執行doOnTouchEnd方法

一、判斷是否是tap事件

```javascript
// 時間小於300ms,click事件延遲300ms觸發
// 水平位移和垂直位移棟小於5像素
if (dragDuration < 300) {
    var fireTap = Math.abs(offsetLeft) < 5 && Math.abs(offsetTop < 5);
    if (isNaN(offsetLeft) || isNaN(offsetTop)) {
        fireTap = true;
    }
    if (fireTap) {
        console.log('tap');
    }
}
```

二、判斷方向

```javascript
// 如果事件間隔小於300ms但是滑出屏幕,直接返回
if (dragDuration < 300 && dragState.currentLeft === undefined) return;

// 如果事件間隔小於300ms 或者 滑動位移超過屏幕寬度 1/2, 根據位移判斷方向
if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) {
    towards = offsetLeft < 0 ? 'next' : 'prev';
}

// 如果非連續,當處於第一頁,不會出現上一頁,當處於最後一頁,不會出現下一頁
if (!continuous) {
    if ((index === 0 && towards === 'prev') 
        || (index === pageCount - 1 && towards === 'next')) {
        towards = null;
    }
}

// 子頁面數量小於2時,不執行滑動動畫
if (children.length < 2) {
    towards = null;
}
```

三、執行動畫

```javascript
// 當沒有options的時候,為自然滑動,也就是定時器滑動
function doAnimate(towards, options) {
    if (children.length === 0) return;
    if (!options && children.length < 2) return;

    var prevPage, nextPage, currentPage, pageWidth, offsetLeft;
    var pageCount = pages.length;

    // 定時器滑動
    if (!options) {
        pageWidth = element.clientWidth;
        currentPage = pages[index];
        prevPage = pages[index - 1];
        nextPage = pages[index + 1];
        if (continuous && pages.length > 1) {
            if (!prevPage) {
                prevPage = pages[pages.length - 1];
            }

            if (!nextPage) {
                nextPage = pages[0];
            }
        }

        // 計算上一頁與下一頁之後
        // 重置位移
        // 參看doOnTouchMove
        // 其實這裡的options 傳與不傳也就是獲取上一頁信息與下一頁信息
        if (prevPage) {
            prevPage.style.display = 'block';
            translate(prevPage, -pageWidth);
        }

        if (nextPage) {
            nextPage.style.display = 'block';
            translate(nextPage, pageWidth);
        }
    } else {
        prevPage = options.prevPage;
        currentPage = options.currentPage;
        nextPage = options.nextPage;
        pageWidth = options.pageWidth;
        offsetLeft = options.offsetLeft;
    }

    var newIndex;
    var oldPage = children[index];

    // 得到滑動之後的新的索引
    if (towards === 'prev') {
        if (index > 0) {
            newIndex = index - 1;
        }
        if (continuous && index === 0) {
            newIndex = pageCount - 1;
        }
    } else if (towards === 'next') {
        if (index < pageCount - 1) {
            newIndex = index + 1;
        }
        if (continuous && index === pageCount - 1) {
            newIndex = 0;
        }
    }

    // 動畫完成之後的回調
    var callback = function() {
        // 得到滑動之後的激活頁面,添加激活class
        // 重新賦值索引
        if (newIndex !== undefined) {
            var newPage = children[newIndex];
            oldPage.classList.remove('is-active');
            newPage.classList.add('is-active');
            index = newIndex
        }

        if (isDone) {
            end();
        }
      
        if (prevPage) {
            prevPage.style.display = '';
        }

        if (nextPage) {
            nextPage.style.display = '';
        }
    }

    setTimeout(function() {
        // 向後滑動
        if (towards === 'next') {
            isDone = true;
            before(currentPage);
            // 當前頁執行動畫,完成後執行callback
            translate(currentPage, -pageWidth, speed, callback);
            if (nextPage) {
                // 下一面移動視野中
                translate(nextPage, 0, speed)
            }
        } else if (towards === 'prev') {
            isDone = true;
            before(currentPage);
            translate(currentPage, pageWidth, speed, callback);
            if (prevPage) {
                translate(prevPage, 0, speed);
            }
        } else {
          // 如果既不是左滑也不是右滑
          isDone = true;
          // 當前頁面依舊處於視野中
          // 上一頁和下一頁滑出
          translate(currentPage, 0, speed, callback);
          if (typeof offsetLeft !== 'undefined') {
              if (prevPage && offsetLeft > 0) {
                    translate(prevPage, pageWidth * -1, speed);
              }
              if (nextPage && offsetLeft < 0) {
                    translate(nextPage, pageWidth, speed);
              }
          } else {
            if (prevPage) {
              translate(prevPage, pageWidth * -1, speed);
            }

            if (nextPage) {
              translate(nextPage, pageWidth, speed);
            }
          }
       }
    }, 10);
}
```

​

後置工作:

清除一次滑動周期中保存的狀態信息

dragging = false;
dragState = {};

總結

整體來說實現原理還是比較簡單的,滑動開始記錄初始位置,計算上一頁與下一頁的應該展示的頁面;滑動中計算位移,計算上一頁下一頁的位移;滑動結束根據位移結果執行相應的動畫。

有一個細節就是,在滑動中transition的效果置為空,是為了防止在滑動中上一頁與下一頁因為過渡存在而位移得不自然,在滑動結束後再給他們加上動畫效果。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第六課標簽 IMG標簽:圖像標記所有瀏覽器都支持 標簽 定義和用法 img 元素向網頁中嵌入一幅圖像。 請註意,從技術上講, 標簽並不會在網頁中插入圖像,而是從網頁上鏈接圖像。 標簽創建的是被引用圖像的占位空間。 標簽有兩個必需的屬性:src 屬性 和 alt 屬性。 src 屬性。 定義和用法 標... ...
  • 第四課的標題及第五課的標題 HN標記 六個不同的 HTML 標題:所有瀏覽器都支持 - 標簽。定義和用法 - 標簽可定義標題。 定義最大的標題。 定義最小的標題。 由於 h 元素擁有確切的語義,因此請您慎重地選擇恰當的標簽層級來構建文檔的結構。因此,請不要利用標題標簽來改變同一行中的字體大小。 相反... ...
  • 1 <html> 2 <head> 3 <title>這是第一節課網頁標題</title> 4 <meta charset="UTF-8"> 5 <meta name="keywords" content="專業,學習,腳本"> 6 </head> 7 <body link="red" vlink= ...
  • 1 2 3 這是第一節課網頁標題 4 5 6 7 你好啊 8 9 10 本章設計的知識: 11 1、個簡單的 HTML 文檔,帶有最基本的必需的元素: 12 13 14 15 文檔的標題 16 17 18 19 文檔的內容... ... 20 21 22 23 所有瀏... ...
  • 案例:嫦娥——尋開心出品:凱迪仕 1、內容:這是一支視頻類H5案例。Loading完畢進入頁面,首屏提示案例最佳觀看方式為先鎖屏再橫屏。點擊開始按鈕播放視頻,視頻講述“葫蘆娃”纏著爺爺講故事,爺爺給他們講了一個嫦娥的故事:嫦娥面對鎮長的兒子天蓬、帥氣的吳剛以及才華橫溢的後裔的追求不知如何選擇,於是決 ...
  • 原因 問題應該是當用戶滑動或切換時,無法判斷是哪個tabs應該進行滑動切換和展示切換動畫。 解決 swipeEnabled 是否允許在標簽之間進行滑動 animationEnabled 是否在更改標簽時動畫 在 根tabs 導航設置里進行設置: 設置後應該就能正常切換了。 ...
  • 一、現象 全屏頁面中的圖表,在很多的時候需要 resize 一把,以適應頁面的大小變化 二、解決 1、引入 : import { Observable } from 'rxjs'; 2、使用(在ngOnInit方法中): ...
  • 下圖是前端工程師圖解: 7e7ec141gdf7d4810f25f&690 前端開發的核心是HTML + CSS + JavaScript。本質上它們構成一個MVC框架,即HTML作為信息模型(Model),CSS控制樣式(View),JavaScript負責調度數據和實現某種展現邏輯(Contro ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...