main.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <div class="el-slider"
  3. :class="{ 'is-vertical': vertical, 'el-slider--with-input': showInput }"
  4. role="slider"
  5. :aria-valuemin="min"
  6. :aria-valuemax="max"
  7. :aria-orientation="vertical ? 'vertical': 'horizontal'"
  8. :aria-disabled="disabled"
  9. >
  10. <el-input-number
  11. v-model="firstValue"
  12. v-if="showInput && !range"
  13. class="el-slider__input"
  14. ref="input"
  15. @change="$nextTick(emitChange)"
  16. :step="step"
  17. :disabled="disabled"
  18. :controls="showInputControls"
  19. :min="min"
  20. :max="max"
  21. :debounce="debounce"
  22. size="small">
  23. </el-input-number>
  24. <div class="el-slider__runway"
  25. :class="{ 'show-input': showInput, 'disabled': disabled }"
  26. :style="runwayStyle"
  27. @click="onSliderClick"
  28. ref="slider">
  29. <div
  30. class="el-slider__bar"
  31. :style="barStyle">
  32. </div>
  33. <slider-button
  34. :vertical="vertical"
  35. v-model="firstValue"
  36. ref="button1">
  37. </slider-button>
  38. <slider-button
  39. :vertical="vertical"
  40. v-model="secondValue"
  41. ref="button2"
  42. v-if="range">
  43. </slider-button>
  44. <div
  45. class="el-slider__stop"
  46. v-for="item in stops"
  47. :style="vertical ? { 'bottom': item + '%' } : { 'left': item + '%' }"
  48. v-if="showStops">
  49. </div>
  50. </div>
  51. </div>
  52. </template>
  53. <script type="text/babel">
  54. import ElInputNumber from 'element-ui/packages/input-number';
  55. import SliderButton from './button.vue';
  56. import Emitter from 'element-ui/src/mixins/emitter';
  57. export default {
  58. name: 'ElSlider',
  59. mixins: [Emitter],
  60. props: {
  61. min: {
  62. type: Number,
  63. default: 0
  64. },
  65. max: {
  66. type: Number,
  67. default: 100
  68. },
  69. step: {
  70. type: Number,
  71. default: 1
  72. },
  73. value: {
  74. type: [Number, Array],
  75. default: 0
  76. },
  77. showInput: {
  78. type: Boolean,
  79. default: false
  80. },
  81. showInputControls: {
  82. type: Boolean,
  83. default: true
  84. },
  85. showStops: {
  86. type: Boolean,
  87. default: false
  88. },
  89. showTooltip: {
  90. type: Boolean,
  91. default: true
  92. },
  93. formatTooltip: Function,
  94. disabled: {
  95. type: Boolean,
  96. default: false
  97. },
  98. range: {
  99. type: Boolean,
  100. default: false
  101. },
  102. vertical: {
  103. type: Boolean,
  104. default: false
  105. },
  106. height: {
  107. type: String
  108. },
  109. debounce: {
  110. type: Number,
  111. default: 300
  112. },
  113. label: {
  114. type: String
  115. }
  116. },
  117. components: {
  118. ElInputNumber,
  119. SliderButton
  120. },
  121. data() {
  122. return {
  123. firstValue: null,
  124. secondValue: null,
  125. oldValue: null,
  126. dragging: false,
  127. sliderSize: 1
  128. };
  129. },
  130. watch: {
  131. value(val, oldVal) {
  132. if (this.dragging ||
  133. Array.isArray(val) &&
  134. Array.isArray(oldVal) &&
  135. val.every((item, index) => item === oldVal[index])) {
  136. return;
  137. }
  138. this.setValues();
  139. },
  140. dragging(val) {
  141. if (!val) {
  142. this.setValues();
  143. }
  144. },
  145. firstValue(val) {
  146. if (this.range) {
  147. this.$emit('input', [this.minValue, this.maxValue]);
  148. } else {
  149. this.$emit('input', val);
  150. }
  151. },
  152. secondValue() {
  153. if (this.range) {
  154. this.$emit('input', [this.minValue, this.maxValue]);
  155. }
  156. },
  157. min() {
  158. this.setValues();
  159. },
  160. max() {
  161. this.setValues();
  162. }
  163. },
  164. methods: {
  165. valueChanged() {
  166. if (this.range) {
  167. return ![this.minValue, this.maxValue]
  168. .every((item, index) => item === this.oldValue[index]);
  169. } else {
  170. return this.value !== this.oldValue;
  171. }
  172. },
  173. setValues() {
  174. const val = this.value;
  175. if (this.range && Array.isArray(val)) {
  176. if (val[1] < this.min) {
  177. this.$emit('input', [this.min, this.min]);
  178. } else if (val[0] > this.max) {
  179. this.$emit('input', [this.max, this.max]);
  180. } else if (val[0] < this.min) {
  181. this.$emit('input', [this.min, val[1]]);
  182. } else if (val[1] > this.max) {
  183. this.$emit('input', [val[0], this.max]);
  184. } else {
  185. this.firstValue = val[0];
  186. this.secondValue = val[1];
  187. if (this.valueChanged()) {
  188. this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue]);
  189. this.oldValue = val.slice();
  190. }
  191. }
  192. } else if (!this.range && typeof val === 'number' && !isNaN(val)) {
  193. if (val < this.min) {
  194. this.$emit('input', this.min);
  195. } else if (val > this.max) {
  196. this.$emit('input', this.max);
  197. } else {
  198. this.firstValue = val;
  199. if (this.valueChanged()) {
  200. this.dispatch('ElFormItem', 'el.form.change', val);
  201. this.oldValue = val;
  202. }
  203. }
  204. }
  205. },
  206. setPosition(percent) {
  207. const targetValue = this.min + percent * (this.max - this.min) / 100;
  208. if (!this.range) {
  209. this.$refs.button1.setPosition(percent);
  210. return;
  211. }
  212. let button;
  213. if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
  214. button = this.firstValue < this.secondValue ? 'button1' : 'button2';
  215. } else {
  216. button = this.firstValue > this.secondValue ? 'button1' : 'button2';
  217. }
  218. this.$refs[button].setPosition(percent);
  219. },
  220. onSliderClick(event) {
  221. if (this.disabled || this.dragging) return;
  222. this.resetSize();
  223. if (this.vertical) {
  224. const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom;
  225. this.setPosition((sliderOffsetBottom - event.clientY) / this.sliderSize * 100);
  226. } else {
  227. const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
  228. this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize * 100);
  229. }
  230. this.emitChange();
  231. },
  232. resetSize() {
  233. if (this.$refs.slider) {
  234. this.sliderSize = this.$refs.slider[`client${ this.vertical ? 'Height' : 'Width' }`];
  235. }
  236. },
  237. emitChange() {
  238. this.$nextTick(() => {
  239. this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value);
  240. });
  241. }
  242. },
  243. computed: {
  244. stops() {
  245. if (this.step === 0) {
  246. process.env.NODE_ENV !== 'production' &&
  247. console.warn('[Element Warn][Slider]step should not be 0.');
  248. return [];
  249. }
  250. const stopCount = (this.max - this.min) / this.step;
  251. const stepWidth = 100 * this.step / (this.max - this.min);
  252. const result = [];
  253. for (let i = 1; i < stopCount; i++) {
  254. result.push(i * stepWidth);
  255. }
  256. if (this.range) {
  257. return result.filter(step => {
  258. return step < 100 * (this.minValue - this.min) / (this.max - this.min) ||
  259. step > 100 * (this.maxValue - this.min) / (this.max - this.min);
  260. });
  261. } else {
  262. return result.filter(step => step > 100 * (this.firstValue - this.min) / (this.max - this.min));
  263. }
  264. },
  265. minValue() {
  266. return Math.min(this.firstValue, this.secondValue);
  267. },
  268. maxValue() {
  269. return Math.max(this.firstValue, this.secondValue);
  270. },
  271. barSize() {
  272. return this.range
  273. ? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
  274. : `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
  275. },
  276. barStart() {
  277. return this.range
  278. ? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
  279. : '0%';
  280. },
  281. precision() {
  282. let precisions = [this.min, this.max, this.step].map(item => {
  283. let decimal = ('' + item).split('.')[1];
  284. return decimal ? decimal.length : 0;
  285. });
  286. return Math.max.apply(null, precisions);
  287. },
  288. runwayStyle() {
  289. return this.vertical ? { height: this.height } : {};
  290. },
  291. barStyle() {
  292. return this.vertical
  293. ? {
  294. height: this.barSize,
  295. bottom: this.barStart
  296. } : {
  297. width: this.barSize,
  298. left: this.barStart
  299. };
  300. }
  301. },
  302. mounted() {
  303. let valuetext;
  304. if (this.range) {
  305. if (Array.isArray(this.value)) {
  306. this.firstValue = Math.max(this.min, this.value[0]);
  307. this.secondValue = Math.min(this.max, this.value[1]);
  308. } else {
  309. this.firstValue = this.min;
  310. this.secondValue = this.max;
  311. }
  312. this.oldValue = [this.firstValue, this.secondValue];
  313. valuetext = `${this.firstValue}-${this.secondValue}`;
  314. } else {
  315. if (typeof this.value !== 'number' || isNaN(this.value)) {
  316. this.firstValue = this.min;
  317. } else {
  318. this.firstValue = Math.min(this.max, Math.max(this.min, this.value));
  319. }
  320. this.oldValue = this.firstValue;
  321. valuetext = this.firstValue;
  322. }
  323. this.$el.setAttribute('aria-valuetext', valuetext);
  324. // label screen reader
  325. this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`);
  326. this.resetSize();
  327. window.addEventListener('resize', this.resetSize);
  328. },
  329. beforeDestroy() {
  330. window.removeEventListener('resize', this.resetSize);
  331. }
  332. };
  333. </script>