av人人干_女狠狠噜天天噜日日噜_国产精品一区二区免费_亚洲国语自产一区第二页_免费av观看_已婚少妇露脸日出白浆_国产精品探花视频_久久国产精品2020免费_国产精品好好热av在线观看_亚洲另类春色校园小说

網(wǎng)站建設(shè)知識

我們將想法與焦點與您一起共享

當前位置:深圳網(wǎng)站建設(shè) > 網(wǎng)站建設(shè)知識> 深圳網(wǎng)站建設(shè)|一步一步實現(xiàn)字母索引導(dǎo)航欄

深圳網(wǎng)站建設(shè)|一步一步實現(xiàn)字母索引導(dǎo)航欄

2017/6/6 8:38:49 獨占網(wǎng)絡(luò) 網(wǎng)站建設(shè)知識
先來看下實現(xiàn)后的效果:

 
這個索引導(dǎo)航欄的效果在很多 APP 中都有應(yīng)用,我也是參考了一些 APP 的效果進行實現(xiàn)。

不過之前接觸移動端頁面開發(fā)較少,所以是邊學邊做,也就把這個過程中的一些東西整理記錄下來。

設(shè)計

這個功能的基本需求可以總結(jié)為一句話:手指在導(dǎo)航欄(也就是 DEMO 上頁面右側(cè)的包含字母的豎條)拖動時,根據(jù)當前手指位置,頁面主體內(nèi)容列表跳轉(zhuǎn)到對應(yīng)字母的內(nèi)容項。

當然,延伸開來,可以是對于已經(jīng)排序的列表,導(dǎo)航欄顯示對應(yīng)的索引字符列表,支持快速跳轉(zhuǎn)到對應(yīng)的索引位置。

這里主要介紹導(dǎo)航欄的實現(xiàn),只看導(dǎo)航欄的話,其實要實現(xiàn)的東西比較簡單,只需要在手指移動時獲取對應(yīng)的字母即可。頁面主體內(nèi)容列表的跳轉(zhuǎn)應(yīng)該交由另一個列表組件實現(xiàn)。

在程序代碼中,組合導(dǎo)航欄和內(nèi)容列表兩個組件,導(dǎo)航欄索引字母更新時,內(nèi)容列表跳轉(zhuǎn)到對應(yīng)的位置。

結(jié)合 DEMO,整體的實現(xiàn)邏輯為:

JavaScript

// 創(chuàng)建一個內(nèi)容列表組件
var itemList = new ItemList(data)

// 創(chuàng)建一個索引導(dǎo)航組件
var indexSidebar = new IndexSidebar()

// 組合兩個組件實現(xiàn)功能
// 監(jiān)聽索引導(dǎo)航組件,一旦索引字符更新,內(nèi)容列表跳轉(zhuǎn)至對應(yīng)的索引字符
indexSidebar.on('charChange', function (ch) {
  itemList.gotoChar(ch)
}) 
接下來,我們一步步實現(xiàn)。

第 1 步:創(chuàng)建 IndexSidebar “類”

我選擇采用實例化“類”的方式來創(chuàng)建新的組件對象,定義“類”,其實就是創(chuàng)建一個構(gòu)造函數(shù)(當然,采用 ES6 語法會更清晰,不過考慮兼容性這里不使用):

JavaScript

function IndexSidebar(options) {
  // TODO 處理 options
  this.initialize(options)
}

IndexSidebar.prototype.initialize = function (options) {
  // TODO 初始化

這里借鑒 Backbone 的模式,將組件初始化的邏輯單獨寫在一個 initialize() 方法中,當然邏輯也可以都寫在構(gòu)造函數(shù)中。

在實現(xiàn)具體的功能前,我們可以先讓前面設(shè)計的代碼跑起來,先補全導(dǎo)航組件的接口方法,支持監(jiān)聽事件:

JavaScript

// 特定事件觸發(fā)時,調(diào)用傳入的回調(diào)函數(shù)并傳入事件數(shù)據(jù)
IndexSidebar.prototype.on = function (event, callback) {
  // TODO 實現(xiàn)事件監(jiān)聽

這里選擇采用事件模式(或者說觀察者模式吧),這樣可以有多個“觀察者”,為了完整,同樣借鑒已有的模式實現(xiàn),我們補全其他會用到的事件接口方法:

JavaScript

// 觸發(fā)特定事件,并給出事件數(shù)據(jù)供監(jiān)聽的回調(diào)函數(shù)使用
IndexSidebar.prototype.trigger = function (event, data) {
  // TODO
}

// 解除事件監(jiān)聽
IndexSidebar.prototype.off = function (event, callback) {
  // TODO

接著來搭個列表組件的架子,同樣是類的模式,不過簡單點,畢竟主要是為了實現(xiàn)索引導(dǎo)航欄組件,列表組件只是輔助:

JavaScript

// 內(nèi)容列表組件
function ItemList(data) {
  return {
    gotoChar: function (ch) {
      // TODO 實現(xiàn)按索引字符跳轉(zhuǎn)功能
    }
  }

這里偷懶了,雖然兼容 new ItemList(data) 的用法,但其實并沒有按照“類”的模式實現(xiàn)。

好了,有了上面的這些代碼,前面的設(shè)計應(yīng)該可以運行了….雖然現(xiàn)在沒什么用。

第 2 步:實現(xiàn)手指拖動更新索引字母

我們先解決導(dǎo)航組件重要的交互功能,也就是手指拖動的動作處理。由于之前沒做過觸摸的功能,我只好先查下相關(guān)的事件用法(當然,盡管沒用過,還是知道有相關(guān)的事件):

Touch events – MDN
touchstart – MDN
touchmove – MDN
touchend – MDN
看了上面這些文檔,我發(fā)現(xiàn) touch 相關(guān)的事件還有個特殊的事件數(shù)據(jù),對應(yīng)的是手指觸摸屏幕的位置:Touch – MDN,顯然這個數(shù)據(jù)是會用到的。

之前做 PC 頁面的時候,也做過類似的鼠標拖動的處理,使用到的瀏覽器事件主要是:mousedown, mousemove, mouseup。大致的處理邏輯是:

鼠標按下(mousedown)時,記錄拖動開始
鼠標移動(mousemove)時,如果拖動開始,則根據(jù)鼠標位置更新并計算相關(guān)數(shù)據(jù)
鼠標松開(mouseup)時,記錄拖動結(jié)束
這個邏輯也可以用在手指觸摸的拖動上。注意一個小細節(jié),手指在屏幕上觸摸時,可能同時有多個位置,所以觸摸事件的位置相關(guān)數(shù)據(jù)是一個列表:TouchList – MDN。不過我這里不關(guān)心,只取列表中的個位置數(shù)據(jù)使用。

這一部分的代碼邏輯實現(xiàn)為:

JavaScript

IndexSidebar.prototype.initEvents = function (options) {
  var el = this.el // el 對應(yīng)導(dǎo)航欄容器元素,初始化過程略
  var touching = false

  el.addEventListener('touchstart', function (e) {
    if (!touching) {
      // 取消缺省行為,否則在 iOS 環(huán)境中會出現(xiàn)頁面上下抖動
      e.preventDefault()
      var t = e.touches[0]
      start(t.clientX, t.clientY)
    }
  }, false)

  // 拖動過程中手指可能會移出導(dǎo)航欄,所以是在 document 上監(jiān)聽
  // 不過貌似在 el 上監(jiān)聽也可以,這個暫不討論了
  // 后面的 touchend 也是類似的緣故
  document.addEventListener('touchmove', function handler(e) {
    if (touching) {
      e.preventDefault()
      var t = e.touches[0]
      move(t.clientX, t.clientY)
    }
  }, false)

  document.addEventListener('touchend', function (e) {
    if (touching) {
      e.preventDefault()
      end()
    }
  }, false)

  // TODO 實現(xiàn)索引字符的更新
  function start(clientX, clientY) {}
  function move(clientX, clientY) {}
  function end() {}

之所以抽出 start(), move(), end() 三個函數(shù),是為了在 PC 瀏覽器器上支持鼠標的拖動,這樣監(jiān)聽鼠標拖動相關(guān)事件時,也能使用這里的邏輯。

怎么計算手指觸摸位置的字符呢?這個我想大家應(yīng)該都能想到,我這里采用的是比較笨的方法,就是根據(jù)觸摸位置計算索引導(dǎo)航欄中的距離近的字符,大致過程為:

已知手指相對屏幕(其實是視口,這里不區(qū)分了)位置(clientX, clientY)和索引字符數(shù)組(chars)
獲取索引導(dǎo)航組件距屏幕部的距離(boxClientTop)和自身的高度(boxHeight)
計算得到手指位置在組件內(nèi)部的相對高度(offsetY):offsetY = clientY - boxClientTop
根據(jù)手指位置的相對高度與組件高度的比例,從索引字符數(shù)組中取出對應(yīng)位置的字符(略,這個不難算)
這里就不貼代碼了,都是一些瑣碎的計算,還要額外考慮手指位置在豎直方向上超出導(dǎo)航欄范圍的情況。

經(jīng)過以上計算,可以得到一個索引字符 ch,接下來要做的就是通知“觀察者”們,字符更新了(如果和上一個索引字符不同的話):

JavaScript

this.trigger('charChange', ch) 
第 3 步:實現(xiàn)組件事件接口

這個其實可以不必多寫,類似的實現(xiàn)有很多。不過為了不依賴其他庫,我選擇自己實現(xiàn)。我就直接貼自己實現(xiàn)的版本了:

JavaScript

/* Event Emitter API */

IndexSidebar.prototype.trigger = function (event, data) {
  var listeners = this._listeners && this._listeners[event]
  if (listeners) {
    listeners.forEach(function (listener) {
      listener(data)
    })
  }
}

IndexSidebar.prototype.on = function (event, callback) {
  this._listeners = this._listeners || {}
  var listeners = this._listeners[event] || (this._listeners[event] = [])
  listeners.push(callback)
}

IndexSidebar.prototype.off = function (event, callback) {
  var listeners = this._listeners && this._listeners[event]
  if (listeners) {
    var i = listeners.indexOf(callback)
    if (i > -1) {
      listeners.splice(i, 1)
      if (listeners.length === 0) {
        this._listeners[event] = null
      }
    }
  }

使用對象屬性 _listeners 來記錄事件監(jiān)聽函數(shù),當然這里可以只實現(xiàn)成單個數(shù)組,不必搞得這么復(fù)雜。不過為了可能的組件擴展的需要,還是這么實現(xiàn)了,這樣如果還需要支持其他類型的事件,例如對外暴露觸摸開始事件“touchStarted”,事件接口這里就不需要修改了。

第 4 步:實現(xiàn)內(nèi)容列表跳轉(zhuǎn)至索引字符

到這里其實索引導(dǎo)航欄組件的開發(fā)已經(jīng)結(jié)束,不過畢竟看不到效果嘛,所以就實現(xiàn)了簡單的內(nèi)容列表組件,從而可以對導(dǎo)航欄組件進行測試。

內(nèi)容列表組件在創(chuàng)建時,傳入了數(shù)據(jù),根據(jù)這些數(shù)據(jù)渲染出列表,并且在渲染的過程中記錄索引,從而在輸出的 HTML 結(jié)構(gòu)上做出標記,以便查找并跳轉(zhuǎn):

JavaScript

// 內(nèi)容列表組件
function ItemList(data) {
  var list = []
  var map = {}
  var html

  html = data.map(function (item) {
    // 數(shù)組中每項為 "Angola 安哥拉" 的形式,且已排序
    var i = item.lastIndexOf(' ')
    var en = item.slice(0, i)
    var cn = item.slice(i + 1)
    var ch = en[0]
    if (map[ch]) {
      return '<li>' + en + '<br>' + cn + '</li>'
    } else {
      // 同一索引字符次出現(xiàn)時,在 HTML 上標記
      map[ch] = true
      return '<li data-ch="' + ch + '">' + en + '<br>' + cn + '</li>'
    }
  }).join('')

  var elItemList = document.querySelector('#item-container ul')
  elItemList.innerHTML = html

  return {
    gotoChar: function (ch) {
      // TODO 實現(xiàn)按索引字符跳轉(zhuǎn)功能
    }
  }

由于已在 HTML 結(jié)構(gòu)上標記了索引字符,所以 gotoChar 的邏輯其實就是找?guī)в袠擞浀脑兀缓笞屍湟苿訚L動到組件部顯示:

JavaScript

  return {
    gotoChar: function (ch) {
      if (ch === '*') {
        // 滾動至部
        elItemList.scrollTop = 0
      } else if (ch === '#') {
        // 滾動至底部
        elItemList.scrollTop = elItemList.scrollHeight
      } else {
        // 滾動至特定索引字符處
        var target = elItemList.querySelector('[data-ch="' + ch + '"]')
        if (target) {
          target.scrollIntoView()
        }
      }
    }
  } 
OK,以上就是所有的邏輯了。

第 5 步:完善索引導(dǎo)航組件

其實基本功能已經(jīng)實現(xiàn),不過既然是想作為開源組件發(fā)布,還是再“包裝”下,主要做了以下幾方面的完善:

支持根據(jù)屏幕高度調(diào)整導(dǎo)航欄的高度計算屏幕高度,和組件距離屏幕部和底部的距離,將索引字符平均分布。
支持組件配置選項,并提供缺省選項由于不想依賴其他庫,且考慮兼容性(不能使用 Object.assign),所以自己實現(xiàn)了:
JavaScript

var defaultOptions = {
  chars: '*ABCDEFGHIJKLMNOPQRSTUVWXYZ#',
  isAdjust: true, // 是否需要自動調(diào)整導(dǎo)航欄高度
  offsetTop: 70,
  offsetBottom: 10,
  lineScale: 0.7,
  charOffsetX: 80,
  charOffsetY: 20
}

function IndexSidebar(options) {
  options = options || {}

  // 遍歷缺省選項逐一處理
  for (var k in defaultOptions) {
    if (defaultOptions.hasOwnProperty(k)) {
      // 未給出選項值時使用缺省選項值
      options[k] = options[k] || defaultOptions[k]
    }
  }

  this.options = options
  this.initialize(options)

支持不同的方式引用組件這個和一般的模塊差不多,不過額外支持了一下 SeaJS(define.cmd):
JavaScript

(function (factory) {

  if (typeof module === 'object' && module.export) {
    module.export = factory()
  } else if (typeof define === 'function' && (define.amd || define.cmd)) {
    define([], factory)
  } else if (typeof window !== 'undefined') {
    window.IndexSidebar = factory()
  }

})(function () {
  // ...
  return IndexSidebar
}) 
總結(jié)

從看到這個需求,到查文檔、設(shè)計、實現(xiàn),以及作為開源工具發(fā)布,用了大概不到 1 天的時間。希望可以有同學能夠從我的這個過程中收獲一些東西吧。

當然,也歡迎提出意見、建議,更歡迎參與完善這個組件: 

后,特別歡迎使用:

Shell 
npm i index-sidebar 

感謝閱讀! 

    深圳網(wǎng)站建設(shè)www.ykfic.cn

始終專注高端網(wǎng)站建設(shè)服務(wù) 網(wǎng)站建設(shè)案例服務(wù)方案聯(lián)系

聯(lián)系我們

135-3000-5572
151-1257-9390

地址:深圳市龍崗區(qū)橫崗街道力嘉創(chuàng)意文化產(chǎn)業(yè)園338

郵箱:chenmh@sz886.com

我們的優(yōu)勢

12年建站服務(wù)經(jīng)驗自主開發(fā)后臺CMS開發(fā)項目不外包無隱形收費

服務(wù)198家上市企業(yè)服務(wù)行業(yè)龍頭超過70家 營銷型網(wǎng)站建設(shè)完備的項目流程管理體系網(wǎng)頁設(shè)計與網(wǎng)站開發(fā)技術(shù)并重

關(guān)于獨占網(wǎng)絡(luò)

十多年來,憑借對設(shè)計的深刻理解,對互聯(lián)網(wǎng)營銷趨勢的敏銳洞察,獨占網(wǎng)絡(luò)不斷修正服務(wù)導(dǎo)向,完善創(chuàng)作品格,始終注重專業(yè)探索,為所有合作企業(yè)機構(gòu)全力以赴,確保網(wǎng)站的視覺體驗與營銷轉(zhuǎn)化能力。

我們是一群對互聯(lián)網(wǎng)抱有執(zhí)著信念的技術(shù)控。從2012年到現(xiàn)在,公司從未建過強大的營銷團隊,卻憑著作品與服務(wù)不斷保持增長,做到了"桃李不言,下自成蹊。" 我們堅信:每個客戶都是一條渠道,每個案例都是一個廣告,為企業(yè)插上翅膀,助力企業(yè)轉(zhuǎn)型升級,我們已做好準備。

專業(yè)團隊為您提供深圳網(wǎng)站建設(shè)、深圳網(wǎng)站制作、深圳營銷型網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)等服務(wù),深圳建網(wǎng)站就找獨占網(wǎng)絡(luò) | 12年專注網(wǎng)站建設(shè)

深圳市獨占網(wǎng)絡(luò)科技有限公司 |備案號:粵ICP備15007128| Copyright 2025,ALL Rights Reserved www.ykfic.cn | Copyright 2025版權(quán)所有
Hi,Are you ready? 如果您有意向
請于我們?nèi)〉寐?lián)系。

有一個互聯(lián)網(wǎng)項目想和我們談?wù)剢幔?br>您可以填寫右邊的表格,讓我們了解您的項目需求,這是一個良好的開始,我們將會盡快與你取得聯(lián)系。當然也歡迎您給我們寫信或是打電話,讓我們聽到你的聲音!

獨占-整合互聯(lián)網(wǎng)營銷

地址:深圳市龍崗區(qū)橫崗地鐵A出口力嘉創(chuàng)意文化產(chǎn)業(yè)園338
大客戶專線:深圳 13530005572
售前QQ:1447567909
E-mail: sales@sz886.cn

合作意向表
您希望我們?yōu)槟峁┦裁捶?wù)?
主站蜘蛛池模板: 广昌县| 宾川县| 太原市| 江口县| 巢湖市| 汾阳市| 泌阳县| 卓资县| 武隆县| 若羌县| 南京市| 平顶山市| 南涧| 铜梁县| 仁怀市| 临沧市| 准格尔旗| 靖宇县| 贡山| 綦江县| 黄骅市| 文山县| 通河县| 芷江| 湘潭县| 神池县| 瑞安市| 西乌珠穆沁旗| 龙口市| 开阳县| 临高县| 益阳市| 遂平县| 济宁市| 宜昌市| 克什克腾旗| 东乡| 溧阳市| 锦屏县| 鄂尔多斯市| 南宁市|