drip-ui
Version:
Lightweight Mobile UI Components built on Vue
439 lines (391 loc) • 13.1 kB
JavaScript
import create from '../utils/create';
import draggable from './draggable';
import translateUtil from './translate';
import { once, addClass, removeClass } from './dom';
import Vue from 'vue';
import emitter from './emitter';
if (!Vue.prototype.$isServer) {
require('raf.js');
}
var rotateElement = function rotateElement(element, angle) {
if (!element) return;
var transformProperty = translateUtil.transformProperty;
element.style[transformProperty] = element.style[transformProperty].replace(/rotateX\(.+?deg\)/gi, '') + (" rotateX(" + angle + "deg)");
};
var ITEM_HEIGHT = 36;
var VISIBLE_ITEMS_ANGLE_MAP = {
3: -45,
5: -20,
7: -15
};
export default create({
render: function render() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('div', {
staticClass: "picker-slot",
"class": _vm.classNames,
style: _vm.flexStyle
}, [_c('div', {
staticClass: "picker-slot-mark_top",
style: {
'height': _vm.markHeight
}
}), !_vm.divider ? _c('div', {
ref: "wrapper",
staticClass: "picker-slot-wrapper",
"class": {
dragging: _vm.dragging
},
style: {
height: _vm.contentHeight + 'PX'
}
}, _vm._l(_vm.mutatingValues, function (itemValue, index) {
return _c('div', {
key: index,
staticClass: "picker-item",
"class": {
'picker-selected': typeof itemValue === 'object' ? itemValue[_vm.valueKey] === _vm.currentValue[_vm.valueKey] : itemValue === _vm.currentValue
},
style: {
height: _vm.itemHeight + 'PX',
lineHeight: _vm.itemHeight + 'PX'
}
}, [_vm._v("\n " + _vm._s(typeof itemValue === 'object' && itemValue[_vm.valueKey] ? itemValue[_vm.valueKey] : itemValue) + "\n ")]);
})) : _vm._e(), _c('div', {
staticClass: "picker-slot-mark_bottom",
style: {
'height': _vm.markHeight
}
}), _vm.divider ? _c('div', [_vm._v(_vm._s(_vm.content))]) : _vm._e()]);
},
name: 'picker-slot',
props: {
values: {
type: Array,
"default": function _default() {
return [];
}
},
value: {},
visibleItemCount: {
type: Number,
"default": 5
},
// 当前数据展示内容的key
valueKey: String,
// 检测当前数据是否变化的key
checkKey: {
type: String,
"default": 'value'
},
rotateEffect: {
type: Boolean,
"default": false
},
divider: {
type: Boolean,
"default": false
},
textAlign: {
type: String,
"default": 'center'
},
flex: {},
className: {},
content: {},
itemHeight: {
type: Number,
"default": ITEM_HEIGHT
},
highlightHeight: {
type: Number,
"default": 50
},
defaultIndex: {
type: Number,
"default": 0,
require: false
}
},
data: function data() {
return {
currentValue: this.value,
mutatingValues: this.values,
dragging: false,
animationFrameId: null
};
},
mixins: [emitter],
computed: {
flexStyle: function flexStyle() {
return {
'flex': this.flex,
'-webkit-box-flex': this.flex,
'-moz-box-flex': this.flex,
'-ms-flex': this.flex
};
},
classNames: function classNames() {
var PREFIX = 'picker-slot-';
var resultArray = [];
if (this.rotateEffect) {
resultArray.push(PREFIX + 'absolute');
}
var textAlign = this.textAlign || 'center';
resultArray.push(PREFIX + textAlign);
if (this.divider) {
resultArray.push(PREFIX + 'divider');
}
if (this.className) {
resultArray.push(this.className);
}
return resultArray.join(' ');
},
contentHeight: function contentHeight() {
return this.itemHeight * (this.visibleItemCount - 1) + 50;
},
markHeight: function markHeight() {
return this.itemHeight * (this.visibleItemCount - 1) / 2 + "PX";
},
valueIndex: function valueIndex() {
if (this.currentValue instanceof Object) {
// 先判断当前数据是否含有传入的或者默认的 checkkey 值
// 有值根据 checkkey 检测数据变化,否则根据 valueKey 检测
var isHasCheckKey = this.currentValue.hasOwnProperty(this.checkKey);
var checkKey = isHasCheckKey ? this.checkKey : this.valueKey;
for (var i = 0, len = this.mutatingValues.length; i < len; i++) {
if (this.currentValue[checkKey] === this.mutatingValues[i][checkKey]) {
return i;
}
}
return -1;
} else {
return this.mutatingValues.indexOf(this.currentValue);
}
},
dragRange: function dragRange() {
var values = this.mutatingValues;
var visibleItemCount = this.visibleItemCount;
var itemHeight = this.itemHeight;
return [-itemHeight * (values.length - Math.ceil(visibleItemCount / 2)), itemHeight * Math.floor(visibleItemCount / 2)];
},
minTranslateY: function minTranslateY() {
return this.itemHeight * (Math.ceil(this.visibleItemCount / 2) - this.mutatingValues.length);
},
maxTranslateY: function maxTranslateY() {
return this.itemHeight * Math.floor(this.visibleItemCount / 2);
}
},
methods: {
value2Translate: function value2Translate(value) {
var _this = this;
var values = this.mutatingValues;
var valueIndex = -1;
if (value instanceof Object) {
values.forEach(function (item, index) {
if (_this.isObjEqual(item, value)) {
valueIndex = index;
}
});
} else {
valueIndex = values.indexOf(value);
}
var offset = Math.floor(this.visibleItemCount / 2);
var itemHeight = this.itemHeight;
if (valueIndex !== -1) {
return (valueIndex - offset) * -itemHeight;
}
},
isObjEqual: function isObjEqual(o1, o2) {
var props1 = Object.keys(o1);
var props2 = Object.keys(o2);
if (props1.length !== props2.length) {
return false;
}
for (var i = 0, max = props1.length; i < max; i++) {
var propName = props1[i];
if (o1[propName] !== o2[propName]) {
return false;
}
}
return true;
},
translate2Value: function translate2Value(translate) {
var itemHeight = this.itemHeight;
translate = Math.round(translate / itemHeight) * itemHeight;
var index = -(translate - Math.floor(this.visibleItemCount / 2) * itemHeight) / itemHeight;
return this.mutatingValues[index];
},
updateRotate: function updateRotate(currentTranslate, pickerItems) {
var _this2 = this;
if (this.divider) return;
var dragRange = this.dragRange;
var wrapper = this.$refs.wrapper;
if (!pickerItems) {
pickerItems = wrapper.querySelectorAll('.picker-item');
}
if (currentTranslate === undefined) {
currentTranslate = translateUtil.getElementTranslate(wrapper).top;
}
var itemsFit = Math.ceil(this.visibleItemCount / 2);
var angleUnit = VISIBLE_ITEMS_ANGLE_MAP[this.visibleItemCount] || -20;
[].forEach.call(pickerItems, function (item, index) {
var itemOffsetTop = index * _this2.itemHeight;
var translateOffset = dragRange[1] - currentTranslate;
var itemOffset = itemOffsetTop - translateOffset;
var percentage = itemOffset / _this2.itemHeight;
var angle = angleUnit * percentage;
if (angle > 180) angle = 180;
if (angle < -180) angle = -180;
rotateElement(item, angle);
if (Math.abs(percentage) > itemsFit) {
addClass(item, 'picker-item-far');
} else {
removeClass(item, 'picker-item-far');
}
});
},
planUpdateRotate: function planUpdateRotate() {
var _this3 = this;
var el = this.$refs.wrapper;
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = requestAnimationFrame(function () {
_this3.updateRotate();
});
once(el, translateUtil.transitionEndProperty, function () {
cancelAnimationFrame(_this3.animationFrameId);
_this3.animationFrameId = null;
});
},
initEvents: function initEvents() {
var _this4 = this;
var el = this.$refs.wrapper;
var dragState = {};
var velocityTranslate, prevTranslate, pickerItems;
draggable(el, {
start: function start(event) {
cancelAnimationFrame(_this4.animationFrameId);
_this4.animationFrameId = null;
dragState = {
range: _this4.dragRange,
start: new Date(),
startLeft: event.pageX,
startTop: event.pageY,
startTranslateTop: translateUtil.getElementTranslate(el).top
};
pickerItems = el.querySelectorAll('.picker-item');
},
drag: function drag(event) {
_this4.dragging = true;
dragState.left = event.pageX;
dragState.top = event.pageY;
var deltaY = dragState.top - dragState.startTop;
var translate = dragState.startTranslateTop + deltaY;
translateUtil.translateElement(el, null, translate);
velocityTranslate = translate - prevTranslate || translate;
prevTranslate = translate;
if (_this4.rotateEffect) {
_this4.updateRotate(prevTranslate, pickerItems);
}
},
end: function end(event) {
_this4.dragging = false;
var momentumRatio = 7;
var currentTranslate = translateUtil.getElementTranslate(el).top;
var duration = new Date() - dragState.start;
var distance = Math.abs(dragState.startTranslateTop - currentTranslate);
var itemHeight = _this4.itemHeight;
var visibleItemCount = _this4.visibleItemCount;
var rect, offset;
if (distance < 6) {
rect = _this4.$el.getBoundingClientRect();
offset = Math.floor((event.clientY - (rect.top + (visibleItemCount - 1) * itemHeight / 2)) / itemHeight) * itemHeight;
if (offset > _this4.maxTranslateY) {
offset = _this4.maxTranslateY;
}
velocityTranslate = 0;
currentTranslate -= offset;
}
var momentumTranslate;
if (duration < 300) {
momentumTranslate = currentTranslate + velocityTranslate * momentumRatio;
}
var dragRange = dragState.range;
_this4.$nextTick(function () {
var translate;
if (momentumTranslate) {
translate = Math.round(momentumTranslate / itemHeight) * itemHeight;
} else {
translate = Math.round(currentTranslate / itemHeight) * itemHeight;
}
translate = Math.max(Math.min(translate, dragRange[1]), dragRange[0]);
translateUtil.translateElement(el, null, translate);
_this4.currentValue = _this4.translate2Value(translate);
if (_this4.rotateEffect) {
_this4.planUpdateRotate();
}
});
dragState = {};
}
});
},
doOnValueChange: function doOnValueChange() {
var value = this.currentValue;
var wrapper = this.$refs.wrapper;
translateUtil.translateElement(wrapper, null, this.value2Translate(value));
},
doOnValuesChange: function doOnValuesChange() {
var _this5 = this;
var el = this.$el;
var items = el.querySelectorAll('.picker-item');
[].forEach.call(items, function (item, index) {
translateUtil.translateElement(item, null, _this5.itemHeight * index);
});
if (this.rotateEffect) {
this.planUpdateRotate();
}
}
},
mounted: function mounted() {
this.ready = true;
this.$emit('input', this.currentValue);
if (!this.divider) {
this.initEvents();
this.doOnValueChange();
}
if (this.rotateEffect) {
this.doOnValuesChange();
}
},
watch: {
values: function values(val) {
this.mutatingValues = val;
},
mutatingValues: function mutatingValues(val) {
var _this6 = this;
if (this.valueIndex === -1) {
this.currentValue = (val || [])[0];
}
if (this.rotateEffect) {
this.$nextTick(function () {
_this6.doOnValuesChange();
});
}
},
currentValue: function currentValue(val) {
this.doOnValueChange();
if (this.rotateEffect) {
this.planUpdateRotate();
}
this.$emit('input', val);
this.dispatch('picker', 'slotValueChange', this);
},
defaultIndex: function defaultIndex(val) {
if (this.mutatingValues[val] !== undefined && this.mutatingValues.length >= val + 1) {
this.currentValue = this.mutatingValues[val];
}
}
}
});