GradientBar.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <template>
  2. <div class="gradient-bar">
  3. <div class="bar" ref="barRef" :style="{ backgroundImage: gradientStyle }" @click="$event => addPoint($event)"></div>
  4. <div class="point"
  5. :class="{ 'active': index === i }"
  6. v-for="(item, i) in points"
  7. :key="item.pos + '-' + i"
  8. :style="{
  9. backgroundColor: item.color,
  10. left: `calc(${item.pos}% - 5px)`,
  11. }"
  12. @mousedown.left="movePoint(i)"
  13. @click.right="removePoint(i)"
  14. ></div>
  15. </div>
  16. </template>
  17. <script lang="ts" setup>
  18. import { ref, computed, watchEffect, useTemplateRef } from 'vue'
  19. import type { GradientColor } from '@/types/slides'
  20. const props = defineProps<{
  21. value: GradientColor[]
  22. index: number
  23. }>()
  24. const emit = defineEmits<{
  25. (event: 'update:value', payload: GradientColor[]): void
  26. (event: 'update:index', payload: number): void
  27. }>()
  28. const points = ref<GradientColor[]>([])
  29. const barRef = useTemplateRef<HTMLElement>('barRef')
  30. watchEffect(() => {
  31. points.value = props.value
  32. if (props.index > props.value.length - 1) emit('update:index', 0)
  33. })
  34. const gradientStyle = computed(() => {
  35. const list = points.value.map(item => `${item.color} ${item.pos}%`)
  36. return `linear-gradient(to right, ${list.join(',')})`
  37. })
  38. const removePoint = (index: number) => {
  39. if (props.value.length <= 2) return
  40. let targetIndex = 0
  41. if (index === props.index) {
  42. targetIndex = (index - 1 < 0) ? 0 : index - 1
  43. }
  44. else if (props.index === props.value.length - 1) {
  45. targetIndex = props.value.length - 2
  46. }
  47. const values = props.value.filter((item, _index) => _index !== index)
  48. emit('update:index', targetIndex)
  49. emit('update:value', values)
  50. }
  51. const movePoint = (index: number) => {
  52. let isMouseDown = true
  53. document.onmousemove = e => {
  54. if (!isMouseDown) return
  55. if (!barRef.value) return
  56. let pos = Math.round((e.clientX - barRef.value.getBoundingClientRect().left) / barRef.value.clientWidth * 100)
  57. if (pos > 100) pos = 100
  58. if (pos < 0) pos = 0
  59. points.value = points.value.map((item, _index) => {
  60. if (_index === index) return { ...item, pos }
  61. return item
  62. })
  63. }
  64. document.onmouseup = () => {
  65. isMouseDown = false
  66. const point = points.value[index]
  67. const _points = [...points.value]
  68. _points.splice(index, 1)
  69. let targetIndex = 0
  70. for (let i = 0; i < _points.length; i++) {
  71. if (point.pos > _points[i].pos) targetIndex = i + 1
  72. }
  73. _points.splice(targetIndex, 0, point)
  74. emit('update:index', targetIndex)
  75. emit('update:value', _points)
  76. document.onmousemove = null
  77. document.onmouseup = null
  78. }
  79. }
  80. const addPoint = (e: MouseEvent) => {
  81. if (props.value.length >= 6) return
  82. if (!barRef.value) return
  83. const pos = Math.round((e.clientX - barRef.value.getBoundingClientRect().left) / barRef.value.clientWidth * 100)
  84. let targetIndex = 0
  85. for (let i = 0; i < props.value.length; i++) {
  86. if (pos > props.value[i].pos) targetIndex = i + 1
  87. }
  88. const color = props.value[targetIndex - 1] ? props.value[targetIndex - 1].color : props.value[targetIndex].color
  89. const values = [...props.value]
  90. values.splice(targetIndex, 0, { pos, color })
  91. emit('update:index', targetIndex)
  92. emit('update:value', values)
  93. }
  94. </script>
  95. <style lang="scss" scoped>
  96. .gradient-bar {
  97. width: calc(100% - 10px);
  98. height: 18px;
  99. padding: 1px 0;
  100. margin: 3px 0;
  101. position: relative;
  102. left: 5px;
  103. .bar {
  104. height: 16px;
  105. border: 1px solid #d9d9d9;
  106. }
  107. .point {
  108. width: 10px;
  109. height: 18px;
  110. background-color: #fff;
  111. position: absolute;
  112. top: 0;
  113. border: 2px solid #fff;
  114. outline: 1px solid #d9d9d9;
  115. box-shadow: 0 0 2px 2px #d9d9d9;
  116. border-radius: 1px;
  117. cursor: pointer;
  118. &.active {
  119. outline: 1px solid $themeColor;
  120. box-shadow: 0 0 2px 2px $themeColor;
  121. }
  122. }
  123. }
  124. </style>