mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
picker component
This commit is contained in:
parent
bdc74d2298
commit
5bae32c726
@ -1,7 +1,29 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pickerColumns: [
|
||||||
|
{
|
||||||
|
values: ['杭州', '宁波', '温州', '嘉兴', '湖州', '绍兴', '金华', '衢州', '舟山', '台州', '丽水']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
## Picker组件
|
## Picker组件
|
||||||
|
|
||||||
模仿iOS中的`UIPickerView`。
|
模仿iOS中的`UIPickerView`。
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
:::demo 基础用法
|
||||||
|
```html
|
||||||
|
<z-picker :columns="pickerColumns"></z-picker>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
"rimraf": "^2.5.4",
|
"rimraf": "^2.5.4",
|
||||||
"run-sequence": "^1.2.2",
|
"run-sequence": "^1.2.2",
|
||||||
"saladcss-bem": "^0.0.1",
|
"saladcss-bem": "^0.0.1",
|
||||||
"sass-loader": "^3.2.3",
|
|
||||||
"style-loader": "^0.13.1",
|
"style-loader": "^0.13.1",
|
||||||
"theaterjs": "^3.0.0",
|
"theaterjs": "^3.0.0",
|
||||||
"transliteration": "^1.1.11",
|
"transliteration": "^1.1.11",
|
||||||
|
51
packages/picker/src/draggable.js
Normal file
51
packages/picker/src/draggable.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
|
const supportTouch = !Vue.prototype.$isServer && 'ontouchstart' in window;
|
||||||
|
|
||||||
|
export default function(element, options) {
|
||||||
|
const moveFn = function(event) {
|
||||||
|
if (options.drag) {
|
||||||
|
options.drag(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const endFn = function(event) {
|
||||||
|
if (!supportTouch) {
|
||||||
|
document.removeEventListener('mousemove', moveFn);
|
||||||
|
document.removeEventListener('mouseup', endFn);
|
||||||
|
}
|
||||||
|
document.onselectstart = null;
|
||||||
|
document.ondragstart = null;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
|
||||||
|
if (options.end) {
|
||||||
|
options.end(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener(supportTouch ? 'touchstart' : 'mousedown', function(event) {
|
||||||
|
if (isDragging) return;
|
||||||
|
document.onselectstart = function() { return false; };
|
||||||
|
document.ondragstart = function() { return false; };
|
||||||
|
|
||||||
|
if (!supportTouch) {
|
||||||
|
document.addEventListener('mousemove', moveFn);
|
||||||
|
document.addEventListener('mouseup', endFn);
|
||||||
|
}
|
||||||
|
isDragging = true;
|
||||||
|
|
||||||
|
if (options.start) {
|
||||||
|
event.preventDefault();
|
||||||
|
options.start(supportTouch ? event.changedTouches[0] || event.touches[0] : event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (supportTouch) {
|
||||||
|
element.addEventListener('touchmove', moveFn);
|
||||||
|
element.addEventListener('touchend', endFn);
|
||||||
|
element.addEventListener('touchcancel', endFn);
|
||||||
|
}
|
||||||
|
};
|
@ -1,14 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="z-picker-column">
|
<div class="z-picker-column">
|
||||||
<div class="z-picker-column-wrapper">
|
<div class="z-picker-column-wrapper" :class="{ dragging: isDragging }" ref="wrapper" :style="{ height: visibleContentHeight + 'px' }">
|
||||||
<div class="z-picker-item">
|
<div
|
||||||
|
v-for="item in currentValues"
|
||||||
|
class="z-picker-column__item"
|
||||||
|
:class="{ 'z-picker-column__item--selected': item === currentValue }"
|
||||||
|
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }">
|
||||||
|
{{item}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import translateUtil from 'src/utils/transition';
|
||||||
|
import draggable from './draggable';
|
||||||
|
|
||||||
const DEFAULT_ITEM_HEIGHT = 44;
|
const DEFAULT_ITEM_HEIGHT = 44;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -40,7 +47,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
currentValue: this.value,
|
currentValue: this.value,
|
||||||
currentValues: this.values,
|
currentValues: this.values,
|
||||||
dragging: false
|
isDragging: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -50,11 +57,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
currentValues(val) {
|
currentValues(val) {
|
||||||
|
if (this.valueIndex === -1) {
|
||||||
|
this.currentValue = (val || [])[0];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
currentValue(val) {
|
currentValue(val) {
|
||||||
|
this.doOnValueChange();
|
||||||
|
|
||||||
this.$emit('change', this);
|
this.$emit('change', this);
|
||||||
|
this.$emit('input', val);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -64,11 +76,128 @@ export default {
|
|||||||
*/
|
*/
|
||||||
visibleContentHeight() {
|
visibleContentHeight() {
|
||||||
return this.itemHeight * this.visibileColumnCount;
|
return this.itemHeight * this.visibileColumnCount;
|
||||||
|
},
|
||||||
|
|
||||||
|
valueIndex() {
|
||||||
|
return this.currentValues.indexOf(this.currentValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
dragRange() {
|
||||||
|
var values = this.currentValues;
|
||||||
|
var visibileColumnCount = this.visibileColumnCount;
|
||||||
|
var itemHeight = this.itemHeight;
|
||||||
|
|
||||||
|
return [ -itemHeight * (values.length - Math.ceil(visibileColumnCount / 2)), itemHeight * Math.floor(visibileColumnCount / 2) ];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
mounted() {
|
||||||
|
this.ready = true;
|
||||||
|
this.$emit('input', this.currentValue);
|
||||||
|
|
||||||
|
this.initEvents();
|
||||||
|
this.doOnValueChange();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
value2Translate(value) {
|
||||||
|
let values = this.currentValues;
|
||||||
|
let valueIndex = values.indexOf(value);
|
||||||
|
let offset = Math.floor(this.visibileColumnCount / 2);
|
||||||
|
let itemHeight = this.itemHeight;
|
||||||
|
|
||||||
|
if (valueIndex !== -1) {
|
||||||
|
return (valueIndex - offset) * -itemHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
translate2Value(translate) {
|
||||||
|
let itemHeight = this.itemHeight;
|
||||||
|
translate = Math.round(translate / itemHeight) * itemHeight;
|
||||||
|
|
||||||
|
let index = -(translate - Math.floor(this.visibileColumnCount / 2) * itemHeight) / itemHeight;
|
||||||
|
|
||||||
|
return this.currentValues[index];
|
||||||
|
},
|
||||||
|
|
||||||
|
initEvents() {
|
||||||
|
var el = this.$refs.wrapper;
|
||||||
|
var dragState = {};
|
||||||
|
|
||||||
|
var velocityTranslate, prevTranslate, pickerItems;
|
||||||
|
|
||||||
|
draggable(el, {
|
||||||
|
start: (event) => {
|
||||||
|
dragState = {
|
||||||
|
range: this.dragRange,
|
||||||
|
start: new Date(),
|
||||||
|
startLeft: event.pageX,
|
||||||
|
startTop: event.pageY,
|
||||||
|
startTranslateTop: translateUtil.getElementTranslate(el).top
|
||||||
|
};
|
||||||
|
pickerItems = el.querySelectorAll('.z-picker-item');
|
||||||
|
},
|
||||||
|
|
||||||
|
drag: (event) => {
|
||||||
|
this.isDragging = true;
|
||||||
|
|
||||||
|
dragState.left = event.pageX;
|
||||||
|
dragState.top = event.pageY;
|
||||||
|
|
||||||
|
let deltaY = dragState.top - dragState.startTop;
|
||||||
|
let translate = dragState.startTranslateTop + deltaY;
|
||||||
|
|
||||||
|
translateUtil.translateElement(el, null, translate);
|
||||||
|
|
||||||
|
velocityTranslate = translate - prevTranslate || translate;
|
||||||
|
|
||||||
|
prevTranslate = translate;
|
||||||
|
},
|
||||||
|
|
||||||
|
end: () => {
|
||||||
|
if (this.isDragging) {
|
||||||
|
this.isDragging = false;
|
||||||
|
|
||||||
|
var momentumRatio = 7;
|
||||||
|
var currentTranslate = translateUtil.getElementTranslate(el).top;
|
||||||
|
var duration = new Date() - dragState.start;
|
||||||
|
|
||||||
|
var momentumTranslate;
|
||||||
|
if (duration < 300) {
|
||||||
|
momentumTranslate = currentTranslate + velocityTranslate * momentumRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dragRange = dragState.range;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
var translate;
|
||||||
|
var itemHeight = this.itemHeight;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.currentValue = this.translate2Value(translate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dragState = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
doOnValueChange() {
|
||||||
|
let value = this.currentValue;
|
||||||
|
let wrapper = this.$refs.wrapper;
|
||||||
|
|
||||||
|
translateUtil.translateElement(wrapper, null, this.value2Translate(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="z-picker">
|
<div class="z-picker">
|
||||||
<div class="z-picker-toolbar">
|
<div class="z-picker__toolbar">
|
||||||
<slot>
|
<slot>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="z-picker-columns">
|
<div class="z-picker__columns" :class="['z-picker__columns--' + columns.length]">
|
||||||
<picker-column
|
<picker-column
|
||||||
v-for="(item, index) in columns"
|
v-for="(item, index) in columns"
|
||||||
v-model="values[index]"
|
v-model="values[index]"
|
||||||
@ -69,7 +69,7 @@ export default {
|
|||||||
let values = [];
|
let values = [];
|
||||||
|
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
values.push(column.value || column[column.defaultIndex || 0]);
|
values.push(column.value || column.values[column.defaultIndex || 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
|
@ -8,4 +8,5 @@
|
|||||||
@import './field.pcss';
|
@import './field.pcss';
|
||||||
@import './icon.pcss';
|
@import './icon.pcss';
|
||||||
@import './popup.pcss';
|
@import './popup.pcss';
|
||||||
|
@import './picker.pcss';
|
||||||
@import './switch.pcss';
|
@import './switch.pcss';
|
||||||
|
73
packages/zanui-css/src/picker.pcss
Normal file
73
packages/zanui-css/src/picker.pcss
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
@component-namespace z {
|
||||||
|
@b picker {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@e toolbar {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@e columns {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@m 1 {
|
||||||
|
.z-picker-column {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@m 2 {
|
||||||
|
.z-picker-column {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@m 2 {
|
||||||
|
.z-picker-column {
|
||||||
|
width: 33.333%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@b picker-column {
|
||||||
|
font-size: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
max-height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
@e item {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
padding: 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #707274;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition-duration: .3s;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
|
||||||
|
@m selected {
|
||||||
|
color: #000;
|
||||||
|
transform: translate3d(0, 0, 0) rotateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-column-wrapper {
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-column-wrapper.dragging,
|
||||||
|
.picker-column-wrapper.dragging .picker-item {
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
}
|
100
src/utils/transition.js
Normal file
100
src/utils/transition.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
var exportObj = {};
|
||||||
|
|
||||||
|
if (!Vue.prototype.$isServer) {
|
||||||
|
var docStyle = document.documentElement.style;
|
||||||
|
var engine;
|
||||||
|
var translate3d = false;
|
||||||
|
|
||||||
|
if (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') {
|
||||||
|
engine = 'presto';
|
||||||
|
} else if ('MozAppearance' in docStyle) {
|
||||||
|
engine = 'gecko';
|
||||||
|
} else if ('WebkitAppearance' in docStyle) {
|
||||||
|
engine = 'webkit';
|
||||||
|
} else if (typeof navigator.cpuClass === 'string') {
|
||||||
|
engine = 'trident';
|
||||||
|
}
|
||||||
|
|
||||||
|
var cssPrefix = {trident: '-ms-', gecko: '-moz-', webkit: '-webkit-', presto: '-o-'}[engine];
|
||||||
|
|
||||||
|
var vendorPrefix = {trident: 'ms', gecko: 'Moz', webkit: 'Webkit', presto: 'O'}[engine];
|
||||||
|
|
||||||
|
var helperElem = document.createElement('div');
|
||||||
|
var perspectiveProperty = vendorPrefix + 'Perspective';
|
||||||
|
var transformProperty = vendorPrefix + 'Transform';
|
||||||
|
var transformStyleName = cssPrefix + 'transform';
|
||||||
|
var transitionProperty = vendorPrefix + 'Transition';
|
||||||
|
var transitionStyleName = cssPrefix + 'transition';
|
||||||
|
var transitionEndProperty = vendorPrefix.toLowerCase() + 'TransitionEnd';
|
||||||
|
|
||||||
|
if (helperElem.style[perspectiveProperty] !== undefined) {
|
||||||
|
translate3d = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getTranslate = function(element) {
|
||||||
|
var result = {left: 0, top: 0};
|
||||||
|
if (element === null || element.style === null) return result;
|
||||||
|
|
||||||
|
var transform = element.style[transformProperty];
|
||||||
|
var matches = /translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/ig.exec(transform);
|
||||||
|
if (matches) {
|
||||||
|
result.left = +matches[1];
|
||||||
|
result.top = +matches[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
var translateElement = function(element, x, y) {
|
||||||
|
if (x === null && y === null) return;
|
||||||
|
|
||||||
|
if (element === null || element === undefined || element.style === null) return;
|
||||||
|
|
||||||
|
if (!element.style[transformProperty] && x === 0 && y === 0) return;
|
||||||
|
|
||||||
|
if (x === null || y === null) {
|
||||||
|
var translate = getTranslate(element);
|
||||||
|
if (x === null) {
|
||||||
|
x = translate.left;
|
||||||
|
}
|
||||||
|
if (y === null) {
|
||||||
|
y = translate.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTranslateElement(element);
|
||||||
|
|
||||||
|
if (translate3d) {
|
||||||
|
element.style[transformProperty] += ' translate(' + (x ? (x + 'px') : '0px') + ',' + (y ? (y + 'px') : '0px') + ') translateZ(0px)';
|
||||||
|
} else {
|
||||||
|
element.style[transformProperty] += ' translate(' + (x ? (x + 'px') : '0px') + ',' + (y ? (y + 'px') : '0px') + ')';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancelTranslateElement = function(element) {
|
||||||
|
if (element === null || element.style === null) return;
|
||||||
|
|
||||||
|
var transformValue = element.style[transformProperty];
|
||||||
|
|
||||||
|
if (transformValue) {
|
||||||
|
transformValue = transformValue.replace(/translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/g, '');
|
||||||
|
element.style[transformProperty] = transformValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exportObj = {
|
||||||
|
transformProperty: transformProperty,
|
||||||
|
transformStyleName: transformStyleName,
|
||||||
|
transitionProperty: transitionProperty,
|
||||||
|
transitionStyleName: transitionStyleName,
|
||||||
|
transitionEndProperty: transitionEndProperty,
|
||||||
|
getElementTranslate: getTranslate,
|
||||||
|
translateElement: translateElement,
|
||||||
|
cancelTranslateElement: cancelTranslateElement
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default exportObj;
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user