一个正常的选择器插件是很是细致的,一步一步来描述就是。手指滑动内容跟随手指滚动,当内容到底或触顶的时候就不能在滚动而且内容要一直保持在正确的位置上。css
首先要有一个插件容器,整个插件容器包含渐变背景,选中实线,内容容器。效果相似于下面:html
因此对应的代码以下:vue
<div class="scroller-component" data-role="component">
<div class="scroller-mask" data-role="mask"></div>
<div class="scroller-indicator" data-role="indicator"></div>
<div class="scroller-content" data-role="content">
<div class="scroller-item" data-value='1'>1</div>
<div class="scroller-item" data-value="2">2</div>
<div class="scroller-item" data-value="3">3</div>
<div class="scroller-item" data-value="4">4</div>
<div class="scroller-item" data-value="5">5</div>
<div class="scroller-item" data-value="6">6</div>
<div class="scroller-item" data-value="7">7</div>
<div class="scroller-item" data-value="8">8</div>
<div class="scroller-item" data-value="9">9</div>
<div class="scroller-item" data-value="10">10</div>
<div class="scroller-item" data-value="11">11</div>
<div class="scroller-item" data-value="12">12</div>
<div class="scroller-item" data-value="13">13</div>
<div class="scroller-item" data-value="14">14</div>
<div class="scroller-item" data-value="15">15</div>
<div class="scroller-item" data-value="16">16</div>
<div class="scroller-item" data-value="17">17</div>
<div class="scroller-item" data-value="18">18</div>
<div class="scroller-item" data-value="19">19</div>
<div class="scroller-item" data-value="20">20</div>
</div>
</div>
复制代码
* {
margin: 0;
padding: 0;
}
.scroller-component {
display: block;
position: relative;
height: 238px;
overflow: hidden;
width: 100%;
}
.scroller-content {
position: absolute;
left: 0;
top: 0;
width: 100%;
z-index: 1;
}
.scroller-mask {
position: absolute;
left: 0;
top: 0;
height: 100%;
margin: 0 auto;
width: 100%;
z-index: 3;
transform: translateZ(0px);
background-image:
linear-gradient(to bottom, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)),
linear-gradient(to top, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6));
background-position: top, bottom;
background-size: 100% 102px;
background-repeat: no-repeat;
}
.scroller-item {
text-align: center;
font-size: 16px;
height: 34px;
line-height: 34px;
color: #000;
}
.scroller-indicator {
width: 100%;
height: 34px;
position: absolute;
left: 0;
top: 102px;
z-index: 3;
background-image:
linear-gradient(to bottom, #d0d0d0, #d0d0d0, transparent, transparent),
linear-gradient(to top, #d0d0d0, #d0d0d0, transparent, transparent);
background-position: top, bottom;
background-size: 100% 1px;
background-repeat: no-repeat;
}
.scroller-item {
line-clamp: 1;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
}
复制代码
css 代码主要做为样式展现,经过外链的方式引入。这里就不作过多的解释。css3
let component = document.querySelector('[data-role=component]')
let touchStartHandler = (e) => { }
let touchMoveHandler = (e) => { }
let touchEndHandler = (e) => { }
component.addEventListener('touchstart', touchStartHandler)
component.addEventListener('touchmove', touchMoveHandler)
component.addEventListener('touchend', touchEndHandler)
复制代码
这样当手指触摸到 component 插件容器的时候就会触发开始,移动,结束事件。git
手指上滑内容上滑,手指下拉内容下拉。只须要控制 content 的位置改动的距离跟手指滑动的距离保持一致便可。这里用到了 transform 样式的 translate3d(x, y, z) 属性。其中 x, z 保持不变,y的值就是手指移动的值。github
咱们继续作拆解,当手指下拉时 content 位置下移就会跟手势保持一致。也就是 y 值变大(须要注意 y 轴正方向是往下的)。手指上拉正好与下拉上滑。当再次下拉或上拉时内容要在原来的基础上保持不变。所以咱们须要一个全局变量 __scrollTop 保存这个值。这个值等于用户每次上拉下拉值的和,因此咱们须要求出来用户每次上拉下拉的值。web
拆解用户上拉的值,当用户手触摸到屏幕的时候就会触发 touchstart 事件,移动的时候会触发 touchmove 事件。离开的时候会触发 touchend 事件。用户上拉的初始值确定是触发 touchstart 时手指的位置。结束值就是 touchend 时手指的位置。可是这样就不可以作到内容跟随手指实时运动了。因此须要拆解 touchmove 事件segmentfault
touchmove 事件会在用户手指运动的时候不停的触发,也就至关于用户屡次极小的上下移动。因此咱们须要记录下来用户刚开始时触摸的位置。 __startTouchTop 。用手指当前位置减去刚开始触发位置就是用户移动的距离 __scrollTop。具体代码以下数组
let content = component.querySelector('[data-role=content]') // 内容容器
let __startTouchTop = 0 // 记录开始滚动的位置
let __scrollTop = 0 // 记录最终滚动的位置
// 这个方法下面立刻讲解
let __callback = (top) => {
const distance = top
content.style.transform = 'translate3d(0, ' + distance + 'px, 0)'
}
// 这个方法下面立刻讲解
let __publish = (top, animationDuration) => {
__scrollTop = top
__callback(top)
}
let touchStartHandler = (e) => {
e.preventDefault()
const target = e.touches ? e.touches[0] : e
__startTouchTop = target.pageY
}
let touchMoveHandler = (e) => {
const target = e.touches ? e.touches[0] : e
let currentTouchTop = target.pageY
let moveY = currentTouchTop - __startTouchTop
let scrollTop = __scrollTop
scrollTop = scrollTop + moveY
__publish(scrollTop)
__startTouchTop = currentTouchTop
}
复制代码
注意1: touchstart 必需要记录触摸位置, touchend 能够不记录。由于用户第一次触摸的位置和下次触摸的位置在同一个地方的可能性几乎微乎其微,因此须要在 touchstart 里面重置触摸位置。不然当用户从新触摸的时候内容会闪动浏览器
注意2:e.preventDefault() 方法是处理某些浏览器的兼容问题而且可以提升性能。像QQ浏览器用手指下拉的时候会出现浏览器描述致使方法失败。 能够参考文档 segmentfault.com/a/119000001… www.cnblogs.com/ziyunfei/p/…
上面的 touchMoveHandler 方法中出现了 __callback 的方法。这个方法是用来控制内容容器的位置的, __publish 方法是对改变容器位置的一层封装,能够实现跟用户的手指动做同步,也要实现用户手指离开以后位置不正确的判断等。目前先实现跟随用户手指移动
代码到这里,你用浏览器调节成手机模式应该已经能够作到内容跟随鼠标滚动了,可是还存在不少问题,下面会一一把这些问题修复
目前用户能够无限上拉下拉,很明显是不对的。应该当第一个值稍微超越选中实线下方时就不能在下拉了,当最后一个值稍微超越选中实线上方时就不能在上拉了。因此咱们须要俩个值最小滚动值: __minScrollTop 和最大滚动值: __maxScrollTop
计算方式应该是这个样子:用户下拉会产生一个最大值,而最大值应该是第一个元素下拉到中间的位置。中间应该就是元素容器中间的位置
let __maxScrollTop = component.clientHeight / 2 // 滚动最大值
复制代码
最小值应该是用户上拉时最后一个元素达到中间的位置,所以应该是内容容器-最大值。
let __minScrollTop = - (content.offsetHeight - __maxScrollTop) // 滚动最小值
复制代码
最大值最小值有了,只须要在手指上拉下拉的过程当中保证 __scrollTop 不大于或者不小于极值便可,所以在 touchMoveHandler 函数中补充以下代码
if (scrollTop > __maxScrollTop || scrollTop < __minScrollTop) {
if (scrollTop > __maxScrollTop) {
scrollTop = __maxScrollTop
} else {
scrollTop = __minScrollTop
}
}
复制代码
目前手指抬起的时候元素停留的位置是存在问题,这个也很容易理解。由于一个元素是有高度的,当你手指移动的距离只要不是元素高度的整数倍他就会卡在选中实线上。所以咱们只须要对移动的距离除以元素的高度进行四舍五入取整以后再乘以元素的高度就可以保证元素位置是元素高得的倍数了
let indicator = component.querySelector('[data-role=indicator]')
let __itemHeight = parseFloat(window.getComputedStyle(indicator).height)
let touchEndHandler = () => {
let scrollTop = Math.round(__scrollTop / __itemHeight).toFixed(5) * __itemHeight
__publish(scrollTop)
}
复制代码
这样子产生了俩个问题,一是当极值四舍五入以后超越了极值就会出错,二是元素跳动太大用户体验很差。因此须要处理极值状况和添加动画滑动效果
咱们新建一个函数 __scrollTo 专门解决元素位置不对的问题
// 滚动到正确位置的方法
let __scrollTo = (top) => {
top = Math.round((top / __itemHeight).toFixed(5)) * __itemHeight
let newTop = Math.max(Math.min(__maxScrollTop, top), __minScrollTop)
if (top !== newTop) {
if (newTop >= __maxScrollTop) {
top = newTop - __itemHeight / 2
} else {
top = newTop + __itemHeight / 2
}
}
__publish(top, 250) // 这里传入了第二个参数动画时长,先留一个伏笔。后面会讲
}
复制代码
简单分析一下,函数内第一行跟以前的同样。对位置进行四舍五入变成元素高度的倍数。第二行判断元素是否大于极值,若是大于最大值就取最大值,小于最小值就取最小值。当滚动值跟新的滚动值不同的时候说明用户移动超过了极值。而后进行处理。大于等于最大值的时候元素的位置正好超出半个元素高度的,因此减掉高度的一半,小于最小值的时候刚好相反。添加一半
这个比较麻烦,关于动画效果是能够单独开一章来讲的。这里我简单说一下我这个动画的思路吧。尽可能长话短说。
首先讲解一下动画实现的原理,动画能够理解为多张连续的照片快速移动超过眼睛能够捕获的速度就会造成连贯的动做。这就是我理解的动画,像上面的 touchMoveHandler 方法实际上是会被屡次调用的,并且调用频率很是的高,高到了几毫秒调用一次,这个速度你肉眼确定是分辨不出来的,并且每次移动的距离贼短。因此你看起来就有了跟随手指滚动的效果
因此当手指抬起的时候发现位置不正确这个时候应该实现一个滚动到正确位置的减速动画效果。这里我直接将 vux 里面的 animate.js 文件简化了一下直接拿过来用了
let running = {} // 运行
let counter = 1 // 计时器
let desiredFrames = 60 // 每秒多少帧
let millisecondsPerSecond = 1000 // 每秒的毫秒数
const Animate = {
// 中止动画
stop (id) {
var cleared = running[id] != null
if (cleared) {
running[id] = null
}
return cleared
},
// 判断给定的动画是否还在运行
isRunning (id) {
return running[id] != null
},
start (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
let start = Date.now()
let percent = 0 // 百分比
let id = counter++
let dropCounter = 0
let step = function () {
let now = Date.now()
if (!running[id] || (verifyCallback && !verifyCallback(id))) {
running[id] = null
completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false)
return
}
if (duration) {
percent = (now - start) / duration
if (percent > 1) {
percent = 1
}
}
let value = easingMethod ? easingMethod(percent) : percent
if (percent !== 1 && ( !verifyCallback || verifyCallback(id))) {
stepCallback(value)
window.requestAnimationFrame(step)
}
}
running[id] = true
window.requestAnimationFrame(step)
return id
}
}
复制代码
以上代码做为一个js外链单独引入,不知道取什么名就用 animate.js 好了。
简单讲解一下,主要是弄了一个叫 Animate 的对象,里面包含三个属性 stop, isRunning, start。 分别是中止动画,动画是否在执行,开始一个动画。start 是关键,由于其余俩个函数在这个项目中我都没有用过,哈哈。
start 函数包含不少个参数,stepCallback:每次动画执行的时候用户处理的界面元素滚动逻辑;verifyCallback:验证动画是否还须要进行的函数;completedCallback:动画完成时的回调函数;duration:动画时长;easingMethod:规定动画的运动方式,像快进慢出,快进快出等等;root:不用管了,没用到。
结束动画有俩种方式,第一种是传入的动画时长达成,另外一种是验证动画是否还须要执行的函数验证经过。不然动画会一直运动
有了动画函数了,接下来就是如何使用了。这里咱们补充一下 __publish 函数,而且添加一个是否开启动画的全局变量 __isAnimating 和 俩个曲线函数 easeOutCubic, easeInOutCubic
let __isAnimating = false // 是否开启动画
// 开始快后来慢的渐变曲线
let easeOutCubic = (pos) => {
return (Math.pow((pos - 1), 3) + 1)
}
// 以知足开始和结束的动画
let easeInOutCubic = (pos) => {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 3)
}
return 0.5 * (Math.pow((pos - 2), 3) + 2)
}
let __publish = (top, animationDuration) => {
if (animationDuration) {
let oldTop = __scrollTop
let diffTop = top - oldTop
let wasAnimating = __isAnimating
let step = function (percent) {
__scrollTop = oldTop + (diffTop * percent)
__callback(__scrollTop)
}
let verify = function (id) {
return __isAnimating === id
}
let completed = function (renderedFramesPerSecond, animationId, wasFinished) {
if (animationId === __isAnimating) {
__isAnimating = false
}
}
__isAnimating = Animate.start(step, verify, completed, animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic)
} else {
__scrollTop = top
__callback(top)
}
}
复制代码
将上面的代码补充完整你就会发现滚动到正确位置的动画效果实现了,下面就讲讲实现的原理。
这里按照函数执行的顺序讲解吧。 首先是定义的几个变量, oldTop:用来保存元素的错误位置; diffTop: 传入的 top 是元素滚动的正确位置; step, verify, completed 是 Animate 对象须要的三个回调函数。里面的参数先不用管后面会讲,最下面给 __isAnimating 付了个值。 Animate.start 函数是有返回值的,返回值是当前动画的ID
其中须要注意 wasAnimating ? easeOutCubic : easeInOutCubic 这个。意思就是若是原来的动画存在就将 easeInOutCubic(俩头慢中间快的参数传入进去)函数传入进去, 若是不存在就传入进去 easeOutCubic(开始快后来慢)函数传入进去。符合的场景就是你手指快速滑动抬起动画会执行一段时间吧,这个过程动画就是从快到慢的过程,而后动画还没结束你又接着快速滑动是否是又从慢到快了。若是你不接着执行是否是动画就由快到慢结束了。这里为啥传入这俩个参数就不讲解了,彻底能够再开一篇博客进行讲解比较麻烦。
step函数,接受一个 percent 翻译过来是百分比的意思。 下面的第一行代码
__scrollTop = oldTop + (diffTop * percent)
复制代码
能够理解成, 老的位置 + 移动的距离 * 百分比 就是新的位置。百分比一直增大当百分比为百分之百的时候 __scrollTop === top。就实现了一个错误位置到正确位置的过分。
百分比的计算方式是根据时间来计算的,而后被动画曲线进行了加工
if (duration) {
percent = (now - start) / duration
if (percent > 1) {
percent = 1
}
}
let value = easingMethod ? easingMethod(percent) : percent
复制代码
上面的是核心代码。start 是调用Animate.start属性的时候记录的一个当前时间,now是内部函数执行的时候记录的一个当前时间。 now - start 就是通过了多长时间,除以 duration动画时长就能够得出动画时长的百分比。下面判断 easingMethod 是否传入若是传入了就对原本匀速增长的百分比进行加工变成了动画曲线变化的百分比。
首先是 step 函数,每次运动调用的函数。接受了一个 percent ,翻译过来是百分比意思。 在外面我定了一个几个局部变量,分别是 oldTop: , , 正确位置减掉错误位置也就是元素滚动的距离。在 step 函数里赋予 __scrollTop 新值
step函数接受了一个叫百分比的参数。 用处就是当元素不在正确位置的时候会产生一个值 __scrollTop, 而元素应该的正确位置的值是 top,元素移动的距离就是 diffTop = top - oldTop 如何一步一步的移动到这个位置呢。就经过动画函数穿过来的这个百分比参数。这也是为啥在 __scrollTo 方法中调用 __publish 时加入第二个参数动画时长的缘由了,这样就实现了一个自由滚动的动画
verify函数接受一个当前动画的id参数,验证规则就是 __isAnimating === id 时说明开启了下一个动画 __isAnimating 就会改变。致使验证失败,这个时候就会中止上一个动画
completed函数接受好几个参数,第一个参数是每秒多少帧,第二个参数是当前动画id,第三个参数是完成状态。这里主要用到了第二个参数当前动画id。动画完成的时候应该奖动画id变为false不然会一直走验证的逻辑。
像目前内容滑动的距离基本是等于用户手指触摸的距离的,这样就跟实际使用不符合,实际中手指使劲一滑内容也会蹭蹭的滚动。就目前这个样子内容一多也能累死用户,因此须要添加用户使劲滑动内容快速滚动起来的逻辑
首先内容本身快速动起来很明显是有个触发条件的,这里的触发条件是 touchEndHandler 函数执行时的时间减去当最后一次执行 touchMoveHandler 函数的时间小于100毫秒。知足这种状态咱们认为用户开启快速滚动状态。因此添加一个全局变量 __lastTouchMove 来记录最后一次执行 touchMoveHandler 函数的时间。
知道应该快速滚动了,如何判断应该滚动多长的距离呢?想一下当前的条件,有一个 __lastTouchMove 和执行 touchEndHandler 函数的时间。这俩个是否是可以的出来一个时间差。在想一下是否是有个 __scrollTop 滚动的位置,若是在获取到上一个滚动的位置是否是可以获得一个位置差。那位置 / 时间是等于速度的。咱们让 __scrollTop + 速度 是否是能够获得新的位置。而后咱们一直减少速度捡到最后等于 0 是否是就获得了滚动的位置,而且可以根据用户的快速滑动状况的出来应该滚动多长的距离,用户滑的越快速度越快距离越远,相反的用户滑动的速度越慢距离越近
遗憾的是在 touchEndHandler 函数中拿不到目标移动的距离 pageY。因此咱们须要在 touchMoveHandler 方法中作手脚,去记录每次执行这个方法时的时间和位置。因此咱们再添加一个全局变量 __positions 为数组类型。
// 上面提到的俩个全局变量的代码
let __lastTouchMove = 0 // 最后滚动时间记录
let __positions = [] // 记录位置和时间
复制代码
而后咱们将增长 __positions 的代码添加到 touchMoveHandler 方法中
if (__positions.length > 40) {
__positions.splice(0, 20)
}
__positions.push(scrollTop, e.timeStamp)
__publish(scrollTop)
__startTouchTop = currentTouchTop
__lastTouchMove = e.timeStamp
复制代码
其中若是 __positions 的长度超过40咱们就取后20个。由于数组太大占用内存,并且循环遍历的时候还很是浪费时间。根据上面的逻辑咱们手指快速移动不会取时间过长的数据,因此20足够了。当有了宝贵的位置和时间数据咱们就须要在 touchEndHandler 方法中分析出来移动的速度了。这里我将完整的代码先切出来。
let __deceleratingMove = 0 // 减速状态每帧移动的距离
let __isDecelerating = false // 是否开启减速状态
let touchEndHandler = (e) => {
if (e.timeStamp - __lastTouchMove < 100) { // 若是抬起时间和最后移动时间小于 100 证实快速滚动过
let positions = __positions
let endPos = positions.length - 1
let startPos = endPos
// 因为保存的时候位置跟时间都保存了, 因此 i -= 2
// positions[i] > (self.__lastTouchMove - 100) 判断是从何时开始的快速滑动
for (let i = endPos; i > 0 && positions[i] > (__lastTouchMove - 100); i -= 2) {
startPos = i
}
if (startPos !== endPos) {
// 计算这两点之间的相对运动
let timeOffset = positions[endPos] - positions[startPos] // 快速开始时间 - 结束滚动时间
let movedTop = __scrollTop - positions[startPos - 1] // 最终距离 - 快速开始距离
__deceleratingMove = movedTop / timeOffset * (1000 / 60) // 1000 / 60 表明 1秒60每帧 也就是 60fps。玩游戏的可能理解 60fps是啥意思
let minVelocityToStartDeceleration = 4 // 开始减速的最小速度
// 只有速度大于最小加速速度时才会出现下面的动画
if (Math.abs(__deceleratingMove) > minVelocityToStartDeceleration) {
__startDeceleration()
}
}
}
if (!__isDecelerating) {
__scrollTo(__scrollTop)
}
__positions.length = 0
}
复制代码
新添加了俩个全局变量运动速度和减速状态记录。当减速状态为true的时候确定不能执行 __scrollTo 方法的由于这俩个方法是冲突的。因此须要 __isDecelerating 记录一下。里面新定义了一个函数 __startDeceleration。 咱们的减速方法也主要是在这个方法里面实现的。给你一下代码
// 开始减速动画
let __startDeceleration = () => {
let step = () => {
let scrollTop = __scrollTop + __deceleratingMove
let scrollTopFixed = Math.max(Math.min(__maxScrollTop, scrollTop), __minScrollTop) // 不小于最小值,不大于最大值
if (scrollTopFixed !== scrollTop) {
scrollTop = scrollTopFixed
__deceleratingMove = 0
}
if (Math.abs(__deceleratingMove) <= 1) {
if (Math.abs(scrollTop % __itemHeight) < 1) {
__deceleratingMove = 0
}
} else {
__deceleratingMove *= 0.95
}
__publish(scrollTop)
}
let minVelocityToKeepDecelerating = 0.5
let verify = () => {
// 保持减速运行须要多少速度
let shouldContinue = Math.abs(__deceleratingMove) >= minVelocityToKeepDecelerating
return shouldContinue
}
let completed = function (renderedFramesPerSecond, animationId, wasFinished) {
__isDecelerating = false
if (__scrollTop <= __minScrollTop || __scrollTop >= __maxScrollTop) {
__scrollTo(__scrollTop)
return
}
}
__isDecelerating = Animate.start(step, verify, completed)
}
复制代码
当你把这些代码都加进去的时候,选择器插件基本上就已经完成了。下面讲解一下这段让你头痛的代码。
这里面用到了动画,因此确定包含三大回调函数 step, verify, completed。而后一个一个讲解一下
step函数:这个函数是让内容一步一步运动的,这个函数基本上跟滚动到正确位置的函数类似度很高。 新的位置是老位置 __scrollTop 加上每帧移动的位置 __deceleratingMove。 而后让每帧移动的位置一直减小,可是须要注意 scrollTop 不能超出极值,因此作了最大值最小值判断当到达极值的时候就将 __deceleratingMove 赋值为0 。
if (Math.abs(__deceleratingMove) <= 1) {
if (Math.abs(scrollTop % __itemHeight) < 1) {
__deceleratingMove = 0
}
}
复制代码
这段代码,你可能佷懵。他的做用是当滚动的位置没有到达极值的时候如何让他卡在正确位置上。 Math.abs(__deceleratingMove) 这是每帧移动的距离的绝对值。当他小于1的时候说明移动的距离已经很是小了,用户基本上都察觉不到移动了。而后再用新位置对元素高度取余,若是余数为0表示正好卡在正确位置上,可是即便稍微比 0 大那么一丢丢也看不出来,并且基本不会那么巧取到 0,因此当余数知足小于 1 的时候讲每帧移动的距离赋值为0.
verify函数:定义了一个最小每帧移动距离的局部变量 minVelocityToKeepDecelerating, 当 __deceleratingMove 值小于他的时候说明用户基本上不会发现内容还在移动能够停下来了。
completed函数:既然是完成函数就必定要将 __isDecelerating 参数变为false,不然下次进行的不是快速移动内容就无法跑到正确位置上了。这里多加了一步是不是极值的判断,若是是极值就执行 __scrollTo 函数到正确位置上。
代码的正确顺序实在是很差再文中体现出来,因此我将正确代码放到了个人github上,让你们不知道放到哪里的时候好有个参照
这个项目是参考的 vux 作出来的。基本上代码都是对 vux 里面picker的解读。为了更加凸显picker的核心代码,我在里面删除了不少东西像兼容性,双指滑动的代码。有兴趣能够去看看这个项目 github.com/airyland/vu…
注意:这个样式虽然已经知足大部分项目需求了,可是仍是不够漂亮。我看过不少手机端pickder的插件。发现京东的nutui的是最漂亮的,他里面用到了css3的旋转特性把他作的想一个滚轮同样。有兴趣的能够学习更精进一波。 github.com/jdf2e/nutui
选择器控件只是开始,后面我会将他衍生出来地区级联控件,日期控件。还会扩展轮播图,上拉加载下拉刷新,浮层等等控件。而且集成到vue中做为一个手机端框架。有兴趣的能够关注个人另一个项目。目前是什么都没有的,因此很是适合想要学习的人一步一步跟进。 github.com/mrxu0/iphon…
我建立了一个qq群欢迎你们反馈交流:954917384 后期持续不断的获取到新的插件更新文章也欢迎你们关注个人微信公众号: