UNPKG

vivo-ui

Version:

vivo ui component lib for vue

358 lines (326 loc) 16.1 kB
import Tween from '../../tools/tween/index'; import { raf, caf } from '../../tools/prefix/index'; import ResizeSensor from '../../vendor/css-element-queries/ResizeSensor'; (function () { if (typeof document !== 'undefined') { var head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'), css = " .vui-picker { -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -moz-box-orient: vertical; -moz-box-direction: normal; -ms-flex-direction: column; flex-direction: column; min-height: 4rem; height: 100%; color: #000; font-size: .44rem; line-height: 1rem; text-align: center; background: #fff; } .vui-picker ul { margin: 0; padding: 0; list-style-type: none; } .vui-picker, .vui-picker-labels, .vui-picker-content, .vui-picker-column { display: -webkit-box; display: -webkit-flex; display: -moz-box; display: -ms-flexbox; display: flex; } .vui-picker-labels { -webkit-box-flex: 0; -webkit-flex: none; -moz-box-flex: 0; -ms-flex: none; flex: none; border-bottom: 1px solid #d0d0d0; } .vui-picker-labels > li, .vui-picker-column { -webkit-box-flex: 1; -webkit-flex: auto; -moz-box-flex: 1; -ms-flex: auto; flex: auto; width: 0; } .vui-picker-labels > li { white-space: nowrap; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; } .vui-picker-content { -webkit-box-flex: 1; -webkit-flex: auto; -moz-box-flex: 1; -ms-flex: auto; flex: auto; position: relative; } .vui-picker-content::before { content: \"\"; position: absolute; left: 0; right: 0; top: 50%; -webkit-transform: translateY(-50%); -moz-transform: translateY(-50%); -ms-transform: translateY(-50%); -o-transform: translateY(-50%); transform: translateY(-50%); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; height: 1rem; border-top: 1px solid #d0d0d0; border-bottom: 1px solid #d0d0d0; } .vui-picker-column { -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -moz-box-orient: vertical; -moz-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-pack: center; -webkit-justify-content: center; -moz-box-pack: center; -ms-flex-pack: center; justify-content: center; overflow: hidden; } .vui-picker-column ul { height: 1rem; } .vui-picker-column li { display: -webkit-box; display: -webkit-flex; display: -moz-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -moz-box-orient: vertical; -moz-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-pack: center; -webkit-justify-content: center; -moz-box-pack: center; -ms-flex-pack: center; justify-content: center; height: 100%; } .vui-picker-column li::before { content: attr(data-value); white-space: nowrap; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; } /*3D样式*/ .vui-picker-enable3d .vui-picker-column { -webkit-perspective: 2000px; -moz-perspective: 2000px; perspective: 2000px; } .vui-picker-enable3d .vui-picker-column ul { -webkit-transform-style: preserve-3d; -moz-transform-style: preserve-3d; transform-style: preserve-3d; } .vui-picker-enable3d .vui-picker-column li { float: left; width: 100%; height: 100%; visibility: hidden; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .vui-picker-enable3d .vui-picker-column li:not(:first-child) { margin-left: -100%; } .vui-picker-enable3d .vui-picker-column .vui-picker-visible { visibility: visible; } ";style.type = 'text/css';if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); }head.appendChild(style); } })(); var index = { render: function render() { var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('div', { staticClass: "vui-picker", class: { 'vui-picker-enable3d': _vm.enable3d } }, [_vm.labels.length ? _c('ul', { staticClass: "vui-picker-labels" }, _vm._l(_vm.labels, function (label) { return _c('li', [_vm._v(_vm._s(label))]); })) : _vm._e(), _vm._v(" "), _c('ul', { ref: "content", staticClass: "vui-picker-content", style: _vm.maskStyle, on: { "touchmove": function touchmove($event) { $event.preventDefault(); } } }, _vm._l(_vm.columns, function (column, index) { return _c('li', { staticClass: "vui-picker-column", on: { "touchmove": function touchmove($event) { _vm.touchmove($event, index); }, "touchend": _vm.touchend } }, [_c('ul', { style: _vm.style[index] }, _vm._l(column, function (item, pos) { return _c('li', { ref: !index && !pos ? 'item' : undefined, refInFor: true, class: { 'vui-picker-visible': _vm.itemVisible(index, pos) }, style: _vm.itemStyle(pos), attrs: { "data-value": item }, on: { "click": function click($event) { _vm.location(index, pos); } } }); }))]); }))]); }, staticRenderFns: [], name: 'vui-picker', props: { data: [Array, Object], // 数据 selected: Array, // 各列选中的子项 sync: { // 滑动父列时,是否立即同步更新子列,false表示等父列滑动停下来后再更新子列 type: Boolean, default: false }, enable3d: { // 是否开启3d旋转 type: Boolean, default: false } }, data: function data() { return { contentHeight: undefined, // 内容区域的高度 itemHeight: undefined, // 各列的每个子项的高度 offsets: [], // 所有列的偏移量,表示当前滑动到第几个子项了,比如0,1,2.5等 positions: [] // 所有列选择的子项的位置索引,比如0,1 }; }, computed: { style: function style() { var _this = this; return this.offsets.map(function (offset) { if (_this.enable3d) { return { transform: 'translate3d(0,0,' + -_this.radius + 'px) rotateX(' + offset * _this.spacing + 'deg)' }; } return { transform: 'translate3d(0,' + -offset * _this.itemHeight + 'px,0)' }; }); }, maskStyle: function maskStyle() { // 蒙层样式 var pos = (this.contentHeight - this.itemHeight) / 2 / this.contentHeight * 100; return { 'mask-box-image': 'linear-gradient(to bottom, rgba(255, 255, 255, .15), rgba(255, 255, 255, .5) ' + pos + '%, #fff ' + pos + '%, #fff ' + (100 - pos) + '%, rgba(255, 255, 255, .5) ' + (100 - pos) + '%, rgba(255, 255, 255, .15))' }; }, radius: function radius() { // 3d旋转半径 return this.contentHeight / 2; }, spacing: function spacing() { // 3d旋转时每个子项之间的角度间距 return Math.asin(Math.min(this.contentHeight / 2 / this.radius, 1)) / Math.PI * 180 / this.half; }, half: function half() { // 一半区域可见子项的数目 return (this.contentHeight / this.itemHeight - 1) / (this.enable3d ? 1.5 : 2); }, cascaded: function cascaded() { // 是否级联 return !(this.data instanceof Array); }, labels: function labels() { var labels = []; if (!this.cascaded) { this.data.forEach(function (column) { column.name && labels.push(column.name); }); } else { var column = this.data; this.positions.forEach(function (pos) { column.name && labels.push(column.name); column = column.options[pos].children; }); } return labels; }, columns: function columns() { // 各列的数据 if (!this.cascaded) { return this.data.map(function (column) { return column.options; }); } else { var columns = []; var column = this.data; this.positions.forEach(function (pos) { columns.push(column.options.map(function (option) { return option.hasOwnProperty('value') ? option.value : option; })); column = column.options[pos].children; }); return columns; } } }, watch: { data: 'update', selected: 'update', positions: function positions(value) { this.$emit('update', value.slice()); } }, methods: { itemVisible: function itemVisible(index, pos) { // 3d旋转时item是否可见 return this.enable3d && Math.abs(pos - this.offsets[index] - 0.5) <= this.half + 0.5; }, itemStyle: function itemStyle(pos) { if (this.enable3d) { return { transform: 'rotateX(' + -pos * this.spacing + 'deg) translate3d(0,0,' + this.radius + 'px)' }; } }, update: function update() { var _this2 = this; this.stop(true); // 设置positions if (this.selected !== undefined) { this.positions = this.selected.slice(); this.offsets = this.selected.slice(); } else { this.positions = []; this.offsets = []; if (!this.cascaded) { this.data.forEach(function () { _this2.positions.push(0); _this2.offsets.push(0); }); } else { var column = this.data; while (column) { this.positions.push(0); this.offsets.push(0); column = column.options[0].children; } } } }, stop: function stop(_stop) { // 停止滑动 if (this.scrollingIndex !== undefined) { var pos = Math.round(this.offsets[this.scrollingIndex]); var length = this.columns[this.scrollingIndex].length; pos < 0 && (pos = 0); pos >= length && (pos = length - 1); this.updatePosition(this.scrollingIndex, pos, _stop); caf(this.rafId); // requestAnimationFrame还未执行时就可能stop了 this.scrollingIndex = undefined; this.touchId = undefined; } }, touchmove: function touchmove(event, index) { var _this3 = this; if (this.scrollingIndex === undefined) { // 没有列正在滑动 this.scrollingIndex = index; this.touchId = event.changedTouches[0].identifier; this.speed = 0; this.prevTime = Date.now(); this.prevY = event.changedTouches[0].clientY; } else { var touch = [].slice.call(event.changedTouches).filter(function (touch) { return touch.identifier === _this3.touchId; })[0]; if (touch) { // 同一个触点移动 var currentTime = Date.now(); if (currentTime === this.prevTime) { // 滑得过快时这两个时间可能相等 return; } var currentY = touch.clientY; var offset = (this.prevY - currentY) / this.itemHeight; var des = this.offsets[this.scrollingIndex] + offset; // 限制不滑出content区域 if (des > this.columns[this.scrollingIndex].length - (this.enable3d ? 2 : 1) + this.half) { des = this.columns[this.scrollingIndex].length - (this.enable3d ? 2 : 1) + this.half; } else if (des < -this.half + (this.enable3d ? 1 : 0)) { des = -this.half + (this.enable3d ? 1 : 0); } var pos = Math.round(des); this.$set(this.offsets, this.scrollingIndex, des); // 更新值 if (this.sync && pos > -1 && pos < this.columns[this.scrollingIndex].length && pos !== this.positions[this.scrollingIndex]) { this.updatePosition(this.scrollingIndex, pos); } this.speed = offset * (1000 / (currentTime - this.prevTime)); this.prevTime = currentTime; this.prevY = currentY; } else if (this.touchId === undefined && index === this.scrollingIndex) { // 引起列滑动的触点已经移除了,并且在当前滚动的列上继续滑动 this.stop(); // 停止当前滑动列的滑动 } } }, touchend: function touchend(event) { var _this4 = this; if ([].slice.call(event.changedTouches).some(function (touch) { return touch.identifier === _this4.touchId; })) { // 同一个触点移除了 var prev = this.offsets[this.scrollingIndex]; var length = this.columns[this.scrollingIndex].length; var offset = (this.speed > 0 ? 1 : -1) * Math.pow(Math.abs(this.speed), 0.7); // 缓冲变化量 var duration = 40 + Math.min(parseInt(2 * Math.abs(offset)), 40); // 缓冲动画有多少帧 var back = void 0; // 是否回弹 // 获取该变量 switch (true) { case prev < 0: offset = -prev; break; case prev < length - 1: if (this.speed < 0 && prev + offset < 0) { offset = -prev; back = true; } else if (this.speed > 0 && offset + prev > length - 1) { offset = length - 1 - prev; back = true; } else { offset = Math.round(offset + prev) - prev; } break; default: offset = length - 1 - prev; } this.touchId = undefined; this.buffer(prev, offset, duration, back); // 缓冲 } }, buffer: function buffer(b, c, duration, back) { var _this5 = this; /* * t: current time(当前时间); * b: beginning value(初始值); * c: change in value(变化量); * d: duration(持续时间); * back: 是否有回弹 */ var t = 0; var scroll = function scroll() { var offset = (back ? Tween.Back.easeOut : Tween.Cubic.easeOut)(t, b, c, duration); var pos = Math.round(offset); _this5.$set(_this5.offsets, _this5.scrollingIndex, offset); // 更新值 if (!_this5.sync && t === duration || _this5.sync && pos > -1 && pos < _this5.columns[_this5.scrollingIndex].length && pos !== _this5.positions[_this5.scrollingIndex]) { _this5.updatePosition(_this5.scrollingIndex, pos); } t < duration ? _this5.rafId = raf(scroll) : _this5.scrollingIndex = undefined; t++; }; this.rafId = raf(scroll); }, updatePosition: function updatePosition(index, pos, stop) { // 更新列选择的值 // 如果更新位置时停止滑动 stop && this.$set(this.offsets, index, pos); if (!this.cascaded) { this.$set(this.positions, index, pos); } else { this.positions.splice(index); this.positions.push(pos); this.offsets.splice(index + 1); var column = this.data; this.positions.forEach(function (i) { column = column.options[i].children; }); while (column) { this.positions.push(0); this.offsets.push(0); column = column.options[0].children; } } }, location: function location(index, pos) { // 点击了列的某个子项,进行定位 if ([undefined, index].indexOf(this.scrollingIndex) !== -1) { this.stop(); this.scrollingIndex = index; var offset = pos - this.offsets[index]; this.buffer(this.offsets[index], offset, 40 + Math.min(parseInt(2 * Math.abs(offset)), 40)); } }, getSize: function getSize() { // 这里不能使用offsetHeight,因为该属性始终返回四舍五入的整数,不精确,会导致显示错位 this.itemHeight = this.$refs.item[0].getBoundingClientRect().height; this.contentHeight = this.$refs.content.getBoundingClientRect().height; } }, created: function created() { // 初始化positions,offsets,并触发update事件 this.update(); }, mounted: function mounted() { this.getSize(); // 监控尺寸变化 new ResizeSensor(this.$el, this.getSize); // eslint-disable-line no-new } }; export default index;