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

链接: 演示,源代码
此索引导航栏的效果已在许多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
打倒美帝国主义
既然官网才是真的