逐步实现字母索引导航栏

电脑杂谈  发布时间:2020-04-13 21:17:36  来源:网络整理

索导航_索航导航搜索_索航导航卡在什么地方

让我们看一下实施后的效果:

DEMO

链接: 演示,源代码

此索引导航栏的效果已在许多APP中得到应用,我还提到了某些APP的实现效果.

但是,移动终端页面上的开发很少,因此我在学习的同时也进行了学习,并且在此过程中还记录了一些东西.

此功能的基本要求可以概括为一句话: 当手指在导航栏中(即在DEMO上页面右侧的包含字母的垂直栏)上拖动时,根据当前手指位置,页面的主要内容列表将跳到相应的字母内容项.

当然,要扩展它,对于排序列表,导航栏会显示相应的索引字符列表,并支持快速跳转到相应的索引位置.

在这里,我们主要介绍导航栏的实现. 如果仅查看导航栏,则要实现的目标相对简单. 您只需要在手指移动时获取相应的字母即可. 页面主要内容列表的跳转应由另一个列表组件实现.

索导航_索航导航搜索_索航导航卡在什么地方

在程序代码中,导航栏和内容列表的两个组件被组合在一起. 更新导航栏的索引字母后,内容列表将跳到相应位置.

结合DEMO,总体实现逻辑为:

// 创建一个内容列表组件
var itemList = new ItemList(data)
// 创建一个索引导航组件
var indexSidebar = new IndexSidebar()
// 组合两个组件实现功能
// 索引导航组件,一旦索引字符更新,内容列表跳转至对应的索引字符
indexSidebar.on('charChange', function (ch) {
  itemList.gotoChar(ch)
})

接下来,我们逐步实现它.

我选择通过实例化“类”并定义“类”来创建新的组件对象,实际上是创建一个构造函数(当然,ES6语法会更清晰,但此处不使用兼容性):

function IndexSidebar(options) {
  // TODO 处理 options
  this.initialize(options)
}
IndexSidebar.prototype.initialize = function (options) {
  // TODO 初始化
}

在这里看一下Backbone模型,用于组件初始化的逻辑是在initialize()方法中单独编写的. 当然,逻辑也可以写在构造函数中.

在实现特定功能之前,我们可以先运行先前设计的代码,首先完成导航组件的接口方法,并支持事件:

索导航_索航导航搜索_索航导航卡在什么地方

// 特定事件触发时,调用传入的回调函数并传入事件数据
IndexSidebar.prototype.on = function (event, callback) {
  // TODO 实现事件
}

在这里,我们选择使用事件模式(或观察者模式),以便可以有多个“观察者”. 为了完整起见,我们还从现有模式中学习,并补充了将要使用的其他事件接口方法:

// 触发特定事件,并给出事件数据供的回调函数使用
IndexSidebar.prototype.trigger = function (event, data) {
  // TODO
}
// 解除事件
IndexSidebar.prototype.off = function (event, callback) {
  // TODO
}

接下来,让我们建立一个列表组件架子,它也是一个类模型,但是简单来说,毕竟它主要是实现索引导航栏组件,而列表组件只是辅助功能:

// 内容列表组件
function ItemList(data) {
  return {
    gotoChar: function (ch) {
      // TODO 实现按索引字符跳转功能
    }
  }
}

我在这里很懒. 尽管它与新的ItemList(数据)的用法兼容,但它不是根据“类”模式实现的.

好的,有了上面的代码,以前的设计应该可以运行了……尽管现在没有用了.

我们首先解决导航组件最重要的交互功能,即拖动手指的动作. 因为我以前没有做过触摸功能,所以我不得不检查相关事件的使用情况(当然,尽管我没有使用过它们,但我仍然知道有相关事件):

索航导航搜索_索航导航卡在什么地方_索导航

阅读上述文档后,我发现与触摸相关的事件也有一个特殊的事件数据,该数据对应于手指触摸屏幕的位置: Touch-MDN. 显然,将使用此数据.

当我在PC页面上工作时,我也进行了类似的鼠标拖动. 使用的浏览器事件为: mousedown,mousemove,mouseup. 通用的处理逻辑是:

此逻辑还可用于手指触摸拖动. 注意一个小细节,当手指在屏幕上触摸时,可能同时存在多个位置,因此触摸事件的位置相关数据是一个列表: TouchList-MDN. 但我不在乎,只需使用列表中的第一个位置数据即可.

这部分代码的逻辑实现为:

IndexSidebar.prototype.initEvents = function (options) {
  var el = this.el // el 对应导航栏容器元素,初始化过程略
  var touching = false
  el.addEventListener('touchstart', function (e) {
    if (!touching) {
      // 取消缺省行为,否则在 iOS 环境中会出现页面上下抖动
      e.preventDefault()
      var t = e.touches[0]
      start(t.clientX, t.clientY)
    }
  }, false)
  // 拖动过程中手指可能会移出导航栏,所以是在 document 上
  // 不过貌似在 el 上也可以,这个暂不讨论了
  // 后面的 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 实现索引字符的更新
  function start(clientX, clientY) {}
  function move(clientX, clientY) {}
  function end() {}
}

提取三个函数start()索导航,move(),end()的原因是为了在PC浏览器上支持鼠标拖动,因此在监视鼠标拖动相关事件时也可以使用此处的逻辑.

如何计算手指触摸位置的字符?我认为每个人都应该能够想到. 我使用的方法是一种愚蠢的方法,它根据触摸位置计算索引导航栏中最接近的字符. 大概的过程是:

代码未在此处发布,这是一些琐碎的计算,并且还会考虑手指位置在垂直方向上超出导航栏的情况.

索航导航搜索_索导航_索航导航卡在什么地方

经过上述计算,您可以获得索引字符ch. 接下来要做的是通知“观察者”该字符已更新(如果它与先前的索引字符不同):

this.trigger('charChange', ch)

实际上,您不需要编写更多内容,有许多类似的实现. 但是为了不依赖其他库,我选择自己实现. 我刚刚发布了直接实现的版本:

/* 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记录事件侦听器函数,当然,这里可以将其实现为单个数组,而不必太复杂. 但是,出于可能的组件扩展的需要,它仍以这种方式实现,因此,如果您需要支持其他类型的事件,例如公开触摸开始事件“ touchStarted”,则无需在此处修改事件接口

这时,索引导航栏组件的开发实际上已经结束,但是毕竟效果不明显索导航,因此实现了一个简单的内容列表组件,以便可以测试导航栏组件.

创建内容列表组件时,将传入数据,并基于数据呈现列表,并在呈现过程中记录索引,以便将输出HTML结构标记为查找并跳转:

// 内容列表组件
function ItemList(data) {
  var list = []
  var map = {}
  var html
  html = data.map(function (item) {
    // 数组中每项为 "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 {
      // 同一索引字符首次出现时,在 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 实现按索引字符跳转功能
    }
  }
}

由于已经在HTML结构上标记了索引字符,因此gotoChar的逻辑实际上是找到标记的元素,然后将其移到组件顶部以显示:

  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()
        }
      }
    }
  }

好的,这就是所有逻辑.


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/shumachanpin/article-174572-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...