Commit 69576f47f4b8973526b166deb4b777a32425ba93

Authored by 梁灏
1 parent 3a2c6fc7

add Slider component

add Slider component
assets/iview.png

161 KB | W: | H:

159 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
src/components/slider/slider.vue
1 1 <template>
2 2 <div :class="classes">
3   - <Input-number v-if="!range && showInput" :min="min" :max="max" :step="step" :value.sync="value" :disabled="disabled"></Input-number>
4   - <div :class="[prefixCls + '-wrap']" @click="sliderClick">
  3 + <Input-number
  4 + v-if="!range && showInput"
  5 + :min="min"
  6 + :max="max"
  7 + :step="step"
  8 + :value="value"
  9 + :disabled="disabled"
  10 + @on-change="handleInputChange"></Input-number>
  11 + <div :class="[prefixCls + '-wrap']" v-el:slider @click="sliderClick">
5 12 <template v-if="showStops">
6 13 <div :class="[prefixCls + '-stop']" v-for="item in stops" :style="{ 'left': item + '%' }"></div>
7 14 </template>
8 15 <div :class="[prefixCls + '-bar']" :style="barStyle"></div>
9   - <div :class="[prefixCls + '-button-wrap']" v-if="!range">
10   - <Tooltip placement="top" :content="tipFormat(value)">
11   - <div :class="[prefixCls + '-button']"></div>
12   - </Tooltip>
13   - </div>
14   -
  16 + <template v-if="range">
  17 + <div
  18 + :class="[prefixCls + '-button-wrap']"
  19 + :style="{left: firstPosition + '%'}"
  20 + @mousedown="onFirstButtonDown">
  21 + <Tooltip placement="top" :content="tipFormat(value[0])" v-ref:tooltip>
  22 + <div :class="button1Classes"></div>
  23 + </Tooltip>
  24 + </div>
  25 + <div
  26 + :class="[prefixCls + '-button-wrap']"
  27 + :style="{left: secondPosition + '%'}"
  28 + @mousedown="onSecondButtonDown">
  29 + <Tooltip placement="top" :content="tipFormat(value[1])" v-ref:tooltip2>
  30 + <div :class="button2Classes"></div>
  31 + </Tooltip>
  32 + </div>
  33 + </template>
  34 + <template v-else>
  35 + <div
  36 + :class="[prefixCls + '-button-wrap']"
  37 + :style="{left: singlePosition + '%'}"
  38 + @mousedown="onSingleButtonDown">
  39 + <Tooltip placement="top" :content="tipFormat(value)" v-ref:tooltip>
  40 + <div :class="buttonClasses"></div>
  41 + </Tooltip>
  42 + </div>
  43 + </template>
15 44 </div>
16 45 </div>
17 46 </template>
18 47 <script>
19 48 import InputNumber from '../../components/input-number/input-number.vue';
20 49 import Tooltip from '../../components/tooltip/tooltip.vue';
  50 + import { getStyle } from '../../utils/assist';
21 51  
22 52 const prefixCls = 'ivu-slider';
23 53  
... ... @@ -65,7 +95,20 @@
65 95 },
66 96 data () {
67 97 return {
68   - prefixCls: prefixCls
  98 + prefixCls: prefixCls,
  99 + dragging: false,
  100 + firstDragging: false,
  101 + secondDragging: false,
  102 + startX: 0,
  103 + currentX: 0,
  104 + startPos: 0,
  105 + newPos: null,
  106 + oldSingleValue: this.value,
  107 + oldFirstValue: this.value[0],
  108 + oldSecondValue: this.value[1],
  109 + singlePosition: (this.value - this.min) / (this.max - this.min) * 100,
  110 + firstPosition: (this.value[0] - this.min) / (this.max - this.min) * 100,
  111 + secondPosition: (this.value[1] - this.min) / (this.max - this.min) * 100
69 112 }
70 113 },
71 114 computed: {
... ... @@ -73,12 +116,36 @@
73 116 return [
74 117 `${prefixCls}`,
75 118 {
76   - [`${prefixCls}-input`]: this.showInput,
  119 + [`${prefixCls}-input`]: this.showInput && !this.range,
77 120 [`${prefixCls}-range`]: this.range,
78 121 [`${prefixCls}-disabled`]: this.disabled
79 122 }
80 123 ]
81 124 },
  125 + buttonClasses () {
  126 + return [
  127 + `${prefixCls}-button`,
  128 + {
  129 + [`${prefixCls}-button-dragging`]: this.dragging
  130 + }
  131 + ];
  132 + },
  133 + button1Classes () {
  134 + return [
  135 + `${prefixCls}-button`,
  136 + {
  137 + [`${prefixCls}-button-dragging`]: this.firstDragging
  138 + }
  139 + ];
  140 + },
  141 + button2Classes () {
  142 + return [
  143 + `${prefixCls}-button`,
  144 + {
  145 + [`${prefixCls}-button-dragging`]: this.secondDragging
  146 + }
  147 + ];
  148 + },
82 149 barStyle () {
83 150 let style;
84 151  
... ... @@ -97,19 +164,249 @@
97 164 },
98 165 stops() {
99 166 return this.max / this.step;
  167 + },
  168 + sliderWidth () {
  169 + return parseInt(getStyle(this.$els.slider, 'width'), 10);
  170 + }
  171 + },
  172 + watch: {
  173 + value (val) {
  174 + this.$nextTick(() => {
  175 + this.$refs.tooltip.updatePopper();
  176 + if (this.range) {
  177 + this.$refs.tooltip2.updatePopper();
  178 + }
  179 + });
  180 + this.updateValue(val);
100 181 }
101 182 },
102 183 methods: {
103   - sliderClick () {
  184 + updateValue (val, init = false) {
  185 + if (this.range) {
  186 + let value = [...val];
  187 + if (init) {
  188 + if (value[0] > value[1]) {
  189 + value = [this.min, this.max];
  190 + }
  191 + } else {
  192 + if (value[0] > value[1]) {
  193 + value[0] = value[1];
  194 + }
  195 + }
  196 + if (value[0] < this.min) {
  197 + value[0] = this.min;
  198 + }
  199 + if (value[0] > this.max) {
  200 + value[0] = this.max;
  201 + }
  202 + if (value[1] < this.min) {
  203 + value[1] = this.min;
  204 + }
  205 + if (value[1] > this.max) {
  206 + value[1] = this.max;
  207 + }
  208 + if (this.value[0] === value[0] && this.value[1] === value[1]) return;
  209 +
  210 + this.value = value;
  211 + this.setFirstPosition(this.value[0]);
  212 + this.setSecondPosition(this.value[1]);
  213 + } else {
  214 + if (val < this.min) {
  215 + this.value = this.min;
  216 + }
  217 + if (val > this.max) {
  218 + this.value = this.max;
  219 + }
  220 + this.setSinglePosition(this.value);
  221 + }
  222 + },
  223 + sliderClick (event) {
  224 + if (this.disabled) return;
  225 + const currentX = event.clientX;
  226 + const sliderOffsetLeft = this.$els.slider.getBoundingClientRect().left;
  227 + const newPos = (currentX - sliderOffsetLeft) / this.sliderWidth * 100;
104 228  
  229 + if (this.range) {
  230 + let type = '';
  231 + if (newPos <= this.firstPosition) {
  232 + type = 'First';
  233 + } else if (newPos >= this.secondPosition) {
  234 + type = 'Second';
  235 + } else {
  236 + if ((newPos - this.firstPosition) <= (this.secondPosition - newPos)) {
  237 + type = 'First';
  238 + } else {
  239 + type = 'Second';
  240 + }
  241 + }
  242 + this[`change${type}Position`](newPos);
  243 + } else {
  244 + this.changeSinglePosition(newPos);
  245 + }
  246 + },
  247 + // for single use
  248 + onSingleButtonDown (event) {
  249 + if (this.disabled) return;
  250 + this.onSingleDragStart(event);
  251 + window.addEventListener('mousemove', this.onSingleDragging);
  252 + window.addEventListener('mouseup', this.onSingleDragEnd);
  253 + },
  254 + onSingleDragStart (event) {
  255 + this.dragging = true;
  256 + this.startX = event.clientX;
  257 + this.startPos = parseInt(this.singlePosition, 10);
  258 + },
  259 + onSingleDragging (event) {
  260 + if (this.dragging) {
  261 + this.$refs.tooltip.visible = true;
  262 + this.currentX = event.clientX;
  263 + const diff = (this.currentX - this.startX) / this.sliderWidth * 100;
  264 + this.newPos = this.startPos + diff;
  265 + this.changeSinglePosition(this.newPos);
  266 + }
  267 + },
  268 + onSingleDragEnd () {
  269 + if (this.dragging) {
  270 + this.dragging = false;
  271 + this.$refs.tooltip.visible = false;
  272 + this.changeSinglePosition(this.newPos);
  273 + window.removeEventListener('mousemove', this.onSingleDragging);
  274 + window.removeEventListener('mouseup', this.onSingleDragEnd);
  275 + }
  276 + },
  277 + changeSinglePosition (newPos) {
  278 + if (newPos >= 0 && (newPos <= 100)) {
  279 + const lengthPerStep = 100 / ((this.max - this.min) / this.step);
  280 + const steps = Math.round(newPos / lengthPerStep);
  281 +
  282 + this.value = Math.round(steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min);
  283 + this.setSinglePosition(this.value);
  284 + if (!this.dragging) {
  285 + if (this.value !== this.oldSingleValue) {
  286 + this.$emit('on-change', this.value);
  287 + this.oldSingleValue = this.value;
  288 + }
  289 + }
  290 + }
  291 + },
  292 + setSinglePosition (val) {
  293 + this.singlePosition = (val - this.min) / (this.max - this.min) * 100;
  294 + },
  295 + handleInputChange (val) {
  296 + this.value = val;
  297 + this.setSinglePosition(val);
  298 + this.$emit('on-change', this.value);
  299 + },
  300 + // for range use first
  301 + onFirstButtonDown (event) {
  302 + if (this.disabled) return;
  303 + this.onFirstDragStart(event);
  304 + window.addEventListener('mousemove', this.onFirstDragging);
  305 + window.addEventListener('mouseup', this.onFirstDragEnd);
  306 + },
  307 + onFirstDragStart (event) {
  308 + this.firstDragging = true;
  309 + this.startX = event.clientX;
  310 + this.startPos = parseInt(this.firstPosition, 10);
  311 + },
  312 + onFirstDragging (event) {
  313 + if (this.firstDragging) {
  314 + this.$refs.tooltip.visible = true;
  315 + this.currentX = event.clientX;
  316 + const diff = (this.currentX - this.startX) / this.sliderWidth * 100;
  317 + this.newPos = this.startPos + diff;
  318 + this.changeFirstPosition(this.newPos);
  319 + }
  320 + },
  321 + onFirstDragEnd () {
  322 + if (this.firstDragging) {
  323 + this.firstDragging = false;
  324 + this.$refs.tooltip.visible = false;
  325 + this.changeFirstPosition(this.newPos);
  326 + window.removeEventListener('mousemove', this.onFirstDragging);
  327 + window.removeEventListener('mouseup', this.onFirstDragEnd);
  328 + }
  329 + },
  330 + changeFirstPosition (newPos) {
  331 + if (newPos >= 0 && (newPos <= this.secondPosition)) {
  332 + const lengthPerStep = 100 / ((this.max - this.min) / this.step);
  333 + const steps = Math.round(newPos / lengthPerStep);
  334 +
  335 + this.value = [Math.round(steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min), this.value[1]];
  336 + this.setFirstPosition(this.value[0]);
  337 + if (!this.firstDragging) {
  338 + if (this.value[0] !== this.oldFirstValue) {
  339 + this.$emit('on-change', this.value);
  340 + this.oldFirstValue = this.value[0];
  341 + }
  342 + }
  343 + }
  344 + },
  345 + setFirstPosition (val) {
  346 + this.firstPosition = (val - this.min) / (this.max - this.min) * 100;
  347 + },
  348 + // for range use second
  349 + onSecondButtonDown (event) {
  350 + if (this.disabled) return;
  351 + this.onSecondDragStart(event);
  352 + window.addEventListener('mousemove', this.onSecondDragging);
  353 + window.addEventListener('mouseup', this.onSecondDragEnd);
  354 + },
  355 + onSecondDragStart (event) {
  356 + this.secondDragging = true;
  357 + this.startX = event.clientX;
  358 + this.startPos = parseInt(this.secondPosition, 10);
  359 + },
  360 + onSecondDragging (event) {
  361 + if (this.secondDragging) {
  362 + this.$refs.tooltip2.visible = true;
  363 + this.currentX = event.clientX;
  364 + const diff = (this.currentX - this.startX) / this.sliderWidth * 100;
  365 + this.newPos = this.startPos + diff;
  366 + this.changeSecondPosition(this.newPos);
  367 + }
  368 + },
  369 + onSecondDragEnd () {
  370 + if (this.secondDragging) {
  371 + this.secondDragging = false;
  372 + this.$refs.tooltip2.visible = false;
  373 + this.changeSecondPosition(this.newPos);
  374 + window.removeEventListener('mousemove', this.onSecondDragging);
  375 + window.removeEventListener('mouseup', this.onSecondDragEnd);
  376 + }
  377 + },
  378 + changeSecondPosition (newPos) {
  379 + if (newPos >= this.firstPosition && (newPos <= 100)) {
  380 + const lengthPerStep = 100 / ((this.max - this.min) / this.step);
  381 + const steps = Math.round(newPos / lengthPerStep);
  382 +
  383 + this.value = [this.value[0], Math.round(steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min)];
  384 + this.setSecondPosition(this.value[1]);
  385 + if (!this.secondDragging) {
  386 + if (this.value[1] !== this.oldSecondValue) {
  387 + this.$emit('on-change', this.value);
  388 + this.oldSecondValue = this.value[1];
  389 + }
  390 + }
  391 + }
  392 + },
  393 + setSecondPosition (val) {
  394 + this.secondPosition = (val - this.min) / (this.max - this.min) * 100;
105 395 }
106 396 },
107 397 ready () {
108 398 if (this.range) {
109 399 const isArray = Array.isArray(this.value);
110   - if (!isArray || (isArray && this.value.length != 2) || (isArray && (!isNaN(this.value[0]) || !isNaN(this.value[1])))) {
111   - this.value = [0, 0];
  400 + if (!isArray || (isArray && this.value.length != 2) || (isArray && (isNaN(this.value[0]) || isNaN(this.value[1])))) {
  401 + this.value = [this.min, this.max];
  402 + } else {
  403 + this.updateValue(this.value, true);
  404 + }
  405 + } else {
  406 + if (typeof this.value !== 'number') {
  407 + this.value = this.min;
112 408 }
  409 + this.updateValue(this.value);
113 410 }
114 411 }
115 412 }
... ...
src/styles/components/index.less
... ... @@ -24,4 +24,5 @@
24 24 @import "select-dropdown";
25 25 @import "tooltip";
26 26 @import "poptip";
27   -@import "input";
28 27 \ No newline at end of file
  28 +@import "input";
  29 +@import "slider";
29 30 \ No newline at end of file
... ...
src/styles/components/input-number.less
... ... @@ -73,7 +73,7 @@
73 73 line-height: 12px;
74 74 font-size: 14px;
75 75 color: #999;
76   - user-select: none;
  76 + .user-select();
77 77 position: absolute;
78 78 right: 4px;
79 79 .transition(all @transition-time linear);
... ...
src/styles/components/modal.less
... ... @@ -9,7 +9,7 @@
9 9 top: 100px;
10 10  
11 11 &-hidden {
12   - display: none;
  12 + display: none !important;
13 13 }
14 14  
15 15 &-wrap {
... ...
src/styles/components/page.less
... ... @@ -19,7 +19,7 @@
19 19 text-align: center;
20 20 list-style: none;
21 21 background-color: #fff;
22   - user-select: none;
  22 + .user-select();
23 23 cursor: pointer;
24 24 font-family: Arial;
25 25 border: 1px solid @border-color-base;
... ...
src/styles/components/select.less
... ... @@ -15,7 +15,7 @@
15 15 display: block;
16 16 box-sizing: border-box;
17 17 outline: none;
18   - user-select: none;
  18 + .user-select();
19 19 cursor: pointer;
20 20  
21 21 background-color: #fff;
... ...
src/styles/components/slider.less 0 → 100644
  1 +@slider-prefix-cls: ~"@{css-prefix}slider";
  2 +
  3 +.@{slider-prefix-cls} {
  4 + &-wrap{
  5 + width: 100%;
  6 + height: @slider-height;
  7 + margin: @slider-margin;
  8 + background-color: @border-color-split;
  9 + border-radius: @btn-border-radius-small;
  10 + vertical-align: middle;
  11 + position: relative;
  12 + cursor: pointer;
  13 + }
  14 +
  15 + &-button-wrap{
  16 + .square(@slider-button-wrap-size);
  17 + text-align: center;
  18 + background-color: transparent;
  19 + position: absolute;
  20 + top: @slider-button-wrap-offset;
  21 + .transform(translateX(-50%));
  22 +
  23 + .@{tooltip-prefix-cls} {
  24 + display: block;
  25 + .user-select();
  26 + }
  27 + }
  28 +
  29 + &-button{
  30 + width: 12px;
  31 + height: 12px;
  32 + border: 2px solid @slider-color;
  33 + border-radius: 50%;
  34 + background-color: #fff;
  35 + .transition(all @transition-time linear);
  36 +
  37 + &:hover,
  38 + &-dragging
  39 + {
  40 + border-color: @primary-color;
  41 + .transform(scale(1.5));
  42 + }
  43 +
  44 + &:hover{
  45 + cursor: grab;
  46 + }
  47 + &-dragging,
  48 + &-dragging:hover
  49 + {
  50 + cursor: grabbing;
  51 + }
  52 + }
  53 +
  54 + &-bar{
  55 + height: @slider-height;
  56 + background: @slider-color;
  57 + border-radius: @btn-border-radius-small;
  58 + position: absolute;
  59 + }
  60 +}
  61 +
  62 +.@{slider-prefix-cls}-disabled{
  63 + cursor: @cursor-disabled;
  64 +
  65 + .@{slider-prefix-cls}-wrap{
  66 + background-color: @slider-disabled-color;
  67 + cursor: @cursor-disabled;
  68 + }
  69 + .@{slider-prefix-cls}-bar{
  70 + background-color: @slider-disabled-color;
  71 + }
  72 +
  73 + .@{slider-prefix-cls}-button{
  74 + border-color: @slider-disabled-color;
  75 +
  76 + &:hover,
  77 + &-dragging
  78 + {
  79 + border-color: @slider-disabled-color;
  80 + }
  81 + &:hover{
  82 + cursor: @cursor-disabled;
  83 + }
  84 + &-dragging,
  85 + &-dragging:hover
  86 + {
  87 + cursor: @cursor-disabled;
  88 + }
  89 + }
  90 +}
  91 +
  92 +.@{slider-prefix-cls}-input{
  93 + .@{slider-prefix-cls}-wrap{
  94 + width: auto;
  95 + margin-right: 100px;
  96 + }
  97 +
  98 + .@{input-number-prefix-cls}{
  99 + float: right;
  100 + margin-top: -14px;
  101 + }
  102 +}
0 103 \ No newline at end of file
... ...
src/styles/components/switch.less
... ... @@ -11,7 +11,7 @@
11 11 background-color: #ccc;
12 12 position: relative;
13 13 cursor: pointer;
14   - user-select: none;
  14 + .user-select();
15 15 .transition(all @transition-time @ease-in-out);
16 16  
17 17 &-inner {
... ...
src/styles/mixins/button.less
... ... @@ -120,7 +120,7 @@
120 120 border: 1px solid transparent;
121 121 white-space: nowrap;
122 122 line-height: @line-height-base;
123   - user-select: none;
  123 + .user-select();
124 124 .button-size(@btn-padding-base; @btn-font-size; @btn-border-radius);
125 125 .transform(translate3d(0, 0, 0));
126 126 //.transition(all @transition-time linear);
... ...
src/styles/mixins/common.less
... ... @@ -12,4 +12,10 @@
12 12 &::-webkit-input-placeholder {
13 13 color: @color;
14 14 }
  15 +}
  16 +
  17 +.user-select(@type: none) {
  18 + -webkit-user-select: @type;
  19 + -moz-user-select: @type;
  20 + user-select: @type;
15 21 }
16 22 \ No newline at end of file
... ...
src/styles/themes/default/custom.less
1 1 // Prefix
2 2 @css-prefix : ivu-;
3 3 @css-prefix-iconfont : ivu-icon;
  4 +
4 5 // Color
5 6 @primary-color : #3399ff;
6 7 @info-color : #2db7f5;
... ... @@ -119,4 +120,12 @@
119 120 // Animation
120 121 @animation-time : .3s;
121 122 @transition-time : .2s;
122   -@ease-in-out : ease-in-out;
123 123 \ No newline at end of file
  124 +@ease-in-out : ease-in-out;
  125 +
  126 +// Slider
  127 +@slider-color : fade(@primary-color, 80%);
  128 +@slider-height : 4px;
  129 +@slider-margin : 16px 0;
  130 +@slider-button-wrap-size : 18px;
  131 +@slider-button-wrap-offset : -5px;
  132 +@slider-disabled-color : #ccc;
124 133 \ No newline at end of file
... ...
src/utils/assist.js
... ... @@ -52,4 +52,27 @@ export function getScrollBarSize (fresh) {
52 52 }
53 53  
54 54 // watch DOM change
55   -export const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || false;
56 55 \ No newline at end of file
  56 +export const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || false;
  57 +
  58 +const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
  59 +const MOZ_HACK_REGEXP = /^moz([A-Z])/;
  60 +
  61 +function camelCase(name) {
  62 + return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
  63 + return offset ? letter.toUpperCase() : letter;
  64 + }).replace(MOZ_HACK_REGEXP, 'Moz$1');
  65 +}
  66 +// getStyle
  67 +export function getStyle (element, styleName) {
  68 + if (!element || !styleName) return null;
  69 + styleName = camelCase(styleName);
  70 + if (styleName === 'float') {
  71 + styleName = 'cssFloat';
  72 + }
  73 + try {
  74 + var computed = document.defaultView.getComputedStyle(element, '');
  75 + return element.style[styleName] || computed ? computed[styleName] : null;
  76 + } catch(e) {
  77 + return element.style[styleName];
  78 + }
  79 +}
57 80 \ No newline at end of file
... ...
test/routers/message.vue
... ... @@ -29,7 +29,7 @@
29 29 Modal
30 30 },
31 31 props: {
32   -
  32 +
33 33 },
34 34 data () {
35 35 return {
... ...
test/routers/slider.vue
1 1 <template>
2   - <Slider :value="10" :tip-format="format">
3   -
4   - </Slider>
  2 + <div style="width: 400px;margin:100px;">
  3 + {{ value }}
  4 + <Slider @on-change="change" :step="10" show-stops></Slider>
  5 + <Slider :value.sync="value" show-input range @on-change="change" :step="13"></Slider>
  6 + <!--<Slider :max="10"></Slider>-->
  7 + <!--<Slider :step="13"></Slider>-->
  8 + <!--<Slider :step="13" :max="60"></Slider>-->
  9 + </div>
5 10 </template>
6 11 <script>
7 12 import { Slider } from 'iview';
... ... @@ -9,12 +14,15 @@
9 14 components: { Slider },
10 15 data () {
11 16 return {
12   -
  17 + value: [20, 50]
13 18 }
14 19 },
15 20 methods: {
16 21 format (val) {
17 22 return `进度:${val}%`
  23 + },
  24 + change (data) {
  25 + console.log(data)
18 26 }
19 27 }
20 28 }
... ...