共计 4214 个字符,预计需要花费 11 分钟才能阅读完成。
如图所示,要开发一个指标工夫组件;
性能要点:
- 弹窗呼出后,要能返显内部选定的初始值;
- 弹窗操作完后,要能把外部选中的值回调给内部;
交互设定:
- 相似密码箱的操作形式,高低滑动选值;
- 点击“勾销”,弹窗隐没;
- 点击“确认”,选中的值回调给内部;
- 点击其余区域,弹窗隐没;
交互细则:
- 须要在滑动完结时主动修改对齐,使选中文字正好垂直居中;
- 批改年或者月时,日必须要被重置到 1 号,且日期可选项须要从新设置,避免日期谬误,如 2 月 31 日;
组件切分:
- 灰色弹窗阻断区域,点击此处弹窗隐没;
- 黄色回调操作,此处用以触发回调 hide 和 confirm;
- 蓝色滚动单元,此处实现高低滑动和交互细则 1,及滑动后将居中的值回调给内部,反显内部设定的选项;
- 绿色业务区域,此处负责解决根本业务:解析内部传入工夫并存储到外部,设置滚动单元的可选项(如月份列表、日期列表),交互细则中的 2;
一、开发蓝色滚动单元
首先确定交互计划,滑动能够用 scroll 实现,或者 touch+translateY 实现;
计划一 ,scroll 益处是晦涩有惯性(ios),毛病是须要思考节流;定位选中元素会比拟难,须要手动监听 scroll 完结事件、获取 scrollTop 间隔、计算出居中元素;
计划二,用 touch+translateY 益处是管制简略,但触摸完结后没有惯性(或者人工设置惯性(我没这本事),比拟麻烦);
<template>
<div
class="scroll-picker"
ref="scroll-wrapper"
@touchstart="_handleTouchStart"
@touchmove="_handleTouchMove"
@touchend="_handleTouchEnd"
>
<div class="scroll-container" ref="scroll-container">
<div class="picker-shadow"></div>
<div
class="picker-option"
v-for="item in options"
:key="item"
:id="`item-${item}`"
>
{{item}}
</div>
<div class="picker-shadow"></div>
</div>
</div>
</template>
//【根本交互原理】// _handleTouchStart、_handleTouchMove、_handleTouchEnd 作用
//
// _handleTouchStart 记录初始地位,用于后续参照
// _handleTouchMove 更具参照初始地位,计算滚动间隔,并设置到视图
// _handleTouchEnd 交互完结后,须要对滚动间隔做一次改正,避免选中的值未对齐视窗
Ps: 布局如上,“picker-shadow”元素高度设置为两个选项的高度,用于第一个选项和最初一个选中居中用;
Js 交互逻辑
name: "scrollPicker",
props: {options: { type: Array, default: () => []},
content: {type: Number, default: 0},
},
data() {
return {
remPxScale: 16,
moveScale: 10,
activeIndex: null,
startY: 0,
};
},
因为用的是 vue 开发,所以不相熟的能够先看下 vue 入门教程;
remPxScale 用以示意压缩比,我用的 rem 作为根本单位;
moveScale 用来设定滑动放大,减少滑动速度,即我喜爱手指滑动一个像素就能滚动 5 个像素那么长;
activeIndex 以后选中的选项索引;
startY 初始滑动地位,用在_handleTouchStart 中记录;
_handleTouchMove(e) {const currentY = this._getYPosition(e);
const nextPostion = this._calYPosition(currentY - this.startY);
this._setYPosition(nextPostion);
},
以上代码,当滑动手指时,计算滑动的间隔,并计算出须要滚动的间隔,即手指一动了 n 个像素,计算出滚动单元须要 translateY 多少,而后这设置到 ref=”scroll-container”上;
其中外围局部为_calYPosition 的计算,大家能够本人实现,我的实现如下,代码略硬各位一笑了之即可:
/**
* 计算以后须要 translateY 到哪个地位,单位 rem
* @param deltaY NUmber 手指划过了多少像素 px
* @param appendOrignal boolean 是否须要叠加解决地位【废除】*
* @variation deltaRem number 手指滑动间隔换算 单位 rem
* @variation orignalRem number 初始 translate 单位 rem
* @variation maxPosition number,最大滚动间隔,即不能滚动到第一个元素更下面
* @variation minPosition number,最大滚动间隔,即不能滚动到最初一个元素更上面
*/
_calYPosition(deltaY, appendOrignal = true) {const deltaRem = deltaY / (this.remPxScale * this.moveScale);
let orignalRem = 0;
if (appendOrignal) {
try {const transformStr = this.$refs["scroll-container"].style.transform;
const moveRexg = /translateY\((-?\d*\.?\d*)rem\)$/;
orignalRem = transformStr.match(moveRexg)[1] || 0;
} catch (error) {orignalRem = 0;}
}
const finalPostion = Number(deltaRem) + Number(orignalRem);
const maxPosition = 0;
const minPosition = -(this.options.length - 1) * 4;
if (finalPostion < minPosition) {return minPosition;} else if (finalPostion > maxPosition) {return maxPosition;}
return finalPostion;
},
最初,滚动完结须要拉正对其,并把选中的值(即滚动到 c 位的值回调进来,用于上级滚动单元应用);
_handleTouchEnd(e) {const finalY = this._getYPosition(e);
const nextPostion = this._calYPosition(finalY - this.startY);
const index = Math.round(-nextPostion / 4);
this.scrollToIndex(index);
},
…
/**
* 最终的定值函数
* @param index Number 指哪打哪,滚动到第几个选项
* @param shouldEmit boolean 是否须要触发内部回调
* 当为外部点击或者滚动时,须要回调内部,放弃平安,比方抉择月份后,日期须要重置,避免移除,比方 2 月 31 日
* 当为内部设置时,不须要回调内部,因为内部曾经是正确的值了,而且下一级的值,内部曾经解决好;*/
scrollToIndex(index, shouldEmit = true) {this._setYPosition(-index * 4);
this.activeIndex = index;
shouldEmit && this.$emit("pickSelect", this.options[index]);
},
二、开发绿色业务区域
绿色区域的初始值设定和回调都很简略不再赘述,讲一下如何联动各个滚动单元
在页面中,滚动单元应用如下
<div class="picker-item">
<scroll-picker :options=“years":content=“year" @pickSelect="(any) => handleSelect('year', any)"
></scroll-picker>
</div>
<div class="picker-item">
<scroll-picker :options=“months":content=“month" @pickSelect="(any) => handleSelect('month', any)"
></scroll-picker>
</div>
<div class="picker-item">
<scroll-picker :options=“dates":content=“date" @pickSelect="(any) => handleSelect('date', any)"
></scroll-picker>
</div>
其中
options 示意滚动单元的选项,比方月份为 1 -12 等;
content 示意以后选中的年月日,比方 year 我选中为 2020;
@pickSelect 示意滚动单元外部选中值后,回调到业务区域,比方我选中了 2 月,那么必须把 2 月回调给业务区域,业务区域从新计算二月有哪些日子,并强制设定日期为 1 号;
看下 handleSelect 的实现;
handleSelect(type, value) {switch (type) {
case "year":
this.year = value;
this.month = 1;
this.date = 1;
break;
case "month":
this.month = value;
this.dates = TimeUtils.createDates(value);
this.date = 1;
break;
case "date":
this.date = value;
break;
case "hour":
this.hour = value;
break;
case "minute":
this.minute = value;
break;
}
}
如上,根本的几个日期间的束缚都在代码中;
最初间接把业务组件中的 year、month、date 组装下丢给 confirm 回调就行,如果须要展现时和分,如法炮制即可;