robin-color-picker.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <template>
  2. <view class="content">
  3. <robin-editor-header class="head" @cancel="cancel" @save="confirm"></robin-editor-header>
  4. <view class="color-picker">
  5. <view class="color-name">{{ colorName }}</view>
  6. <view class="show-view" :style="{ background: colorName }"></view>
  7. <view class="hue-view" @touchstart="pickHue" @touchmove="pickHue"><text class="anchor" :style="{ left: hueView.anchorLeft + 'px' }"></text></view>
  8. <view class="color-view" @touchstart="pickColor" @touchmove="pickColor" :style="{ backgroundColor: 'hsl(' + hueView.H + ', 100%, 50%)' }">
  9. <text class="anchor" :style="{ top: colorView.anchorTop + 'px', left: colorView.anchorLeft + 'px' }"></text>
  10. </view>
  11. </view>
  12. </view>
  13. </template>
  14. <script>
  15. export default {
  16. inject: ['popup'],
  17. props: {
  18. color: {
  19. type: String,
  20. default: ''
  21. }
  22. },
  23. data() {
  24. return {
  25. hueView: {},
  26. colorView: {},
  27. colorName: '',
  28. hueLeft: 0.5, // 色相选择器初始位置 [0, 1]
  29. anchorTop: 0.5, // 颜色选择器初始 top [0, 1]
  30. anchorLeft: 0.5, // 颜色选择器初始 left [0, 1],
  31. };
  32. },
  33. created() {
  34. this.popup.childrenMsg = this;
  35. },
  36. methods: {
  37. open: function() {
  38. setTimeout(() => {
  39. this.init();
  40. }, this.popup.duration);
  41. },
  42. init() {
  43. const reg = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})*$/;
  44. if (this.color !== '' && reg.test(this.color)) {
  45. this.getColorOffset();
  46. }
  47. Promise.all([this.getHueViewOffset(), this.getColorViewOffset()]).then(() => {
  48. this.colorName = this.getColorString(); // 根据 HLS 计算 RGB 字符串
  49. });
  50. },
  51. getHueViewOffset() {
  52. // 获取色相选择区域尺寸
  53. return new Promise(resolve =>
  54. uni
  55. .createSelectorQuery()
  56. .in(this)
  57. .select('.hue-view')
  58. .boundingClientRect(data => {
  59. this.hueView = {
  60. ...data,
  61. anchorLeft: data.width * this.hueLeft,
  62. H: this.hueLeft * 360
  63. };
  64. resolve();
  65. })
  66. .exec()
  67. );
  68. },
  69. getColorViewOffset() {
  70. // 获取颜色选择区域尺寸
  71. return new Promise(resolve =>
  72. uni
  73. .createSelectorQuery()
  74. .in(this)
  75. .select('.color-view')
  76. .boundingClientRect(data => {
  77. this.colorView = {
  78. ...data,
  79. anchorTop: data.height * this.anchorTop,
  80. anchorLeft: data.width * this.anchorLeft,
  81. S: this.anchorLeft,
  82. L: 1 - this.anchorLeft * 0.5 - this.anchorTop / (this.anchorLeft + 1)
  83. };
  84. resolve();
  85. })
  86. .exec()
  87. );
  88. },
  89. getColorString() {
  90. // 获取 RGB 颜色字符串
  91. const arr = hslToRgb(this.hueView.anchorLeft / this.hueView.width, this.colorView.S, this.colorView.L);
  92. const r = arr[0].toString(16).length === 1 ? `0${arr[0].toString(16)}` : arr[0].toString(16);
  93. const g = arr[1].toString(16).length === 1 ? `0${arr[1].toString(16)}` : arr[1].toString(16);
  94. const b = arr[2].toString(16).length === 1 ? `0${arr[2].toString(16)}` : arr[2].toString(16);
  95. return `#${r.toUpperCase()}${g.toUpperCase()}${b.toUpperCase()}`;
  96. },
  97. getColorOffset() {
  98. var color = this.color.substr(1);
  99. color = color.length == 6 ? color : color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
  100. const r = parseInt('0x' + color.substr(0, 2));
  101. const g = parseInt('0x' + color.substr(2, 2));
  102. const b = parseInt('0x' + color.substr(4, 2));
  103. const arr = rgbToHsl(r, g, b);
  104. this.hueLeft = arr[0];
  105. this.anchorLeft = arr[1];
  106. this.anchorTop = (1 - arr[1] * 0.5 - arr[2]) * (arr[1] + 1);
  107. },
  108. pickColor(e) {
  109. // 选择颜色
  110. const top = e.touches[0].clientY - this.colorView.top;
  111. const left = e.touches[0].clientX - this.colorView.left;
  112. if (top < 0) {
  113. this.colorView.anchorTop = 0;
  114. } else if (top > this.colorView.height) {
  115. this.colorView.anchorTop = this.colorView.height;
  116. } else {
  117. this.colorView.anchorTop = top;
  118. }
  119. if (left < 0) {
  120. this.colorView.anchorLeft = 0;
  121. } else if (left > this.colorView.width) {
  122. this.colorView.anchorLeft = this.colorView.width;
  123. } else {
  124. this.colorView.anchorLeft = e.touches[0].clientX - this.colorView.left;
  125. }
  126. this.colorView.S = this.colorView.anchorLeft / this.colorView.width;
  127. this.colorView.L = this.floor(1 - this.colorView.S * 0.5 - this.colorView.anchorTop / this.colorView.height / (this.colorView.S + 1));
  128. this.colorName = this.getColorString(); // 根据 HLS 计算 RGB 字符串
  129. },
  130. pickHue(e) {
  131. // 选择色相
  132. if (e.touches[0].clientX >= this.hueView.left && e.touches[0].clientX <= this.hueView.right) {
  133. this.hueView.anchorLeft = e.touches[0].clientX - this.hueView.left;
  134. this.hueView.H = (this.hueView.anchorLeft / this.hueView.width) * 360;
  135. this.colorName = this.getColorString(); // 根据 HLS 计算 RGB 字符串
  136. }
  137. },
  138. floor(num) {
  139. return num < 0.09 ? 0 : num;
  140. },
  141. confirm() {
  142. this.$emit('confirm', {
  143. color: this.colorName
  144. });
  145. this.popup.close();
  146. },
  147. cancel() {
  148. this.$emit('cancel');
  149. this.popup.close();
  150. },
  151. close() {}
  152. }
  153. };
  154. function hslToRgb(h, s, l) {
  155. // HSL 转 RGB 方法
  156. var r, g, b;
  157. if (s == 0) {
  158. r = g = b = l; // achromatic
  159. } else {
  160. var hue2rgb = function hue2rgb(p, q, t) {
  161. if (t < 0) t += 1;
  162. if (t > 1) t -= 1;
  163. if (t < 1 / 6) return p + (q - p) * 6 * t;
  164. if (t < 1 / 2) return q;
  165. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  166. return p;
  167. };
  168. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  169. var p = 2 * l - q;
  170. r = hue2rgb(p, q, h + 1 / 3);
  171. g = hue2rgb(p, q, h);
  172. b = hue2rgb(p, q, h - 1 / 3);
  173. }
  174. return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
  175. }
  176. function rgbToHsl(r, g, b) {
  177. (r /= 255), (g /= 255), (b /= 255);
  178. var max = Math.max(r, g, b),
  179. min = Math.min(r, g, b);
  180. var h,
  181. s,
  182. l = (max + min) / 2;
  183. if (max == min) {
  184. h = s = 0; // achromatic
  185. } else {
  186. var d = max - min;
  187. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  188. switch (max) {
  189. case r:
  190. h = (g - b) / d + (g < b ? 6 : 0);
  191. break;
  192. case g:
  193. h = (b - r) / d + 2;
  194. break;
  195. case b:
  196. h = (r - g) / d + 4;
  197. break;
  198. }
  199. h /= 6;
  200. }
  201. var round = function(n, l) {
  202. return Math.round(n * Math.pow(10, l)) / Math.pow(10, l);
  203. };
  204. return [round(h, 3), round(s, 3), round(l, 3)];
  205. }
  206. </script>
  207. <style lang="scss" scoped>
  208. .content {
  209. height: 100%;
  210. display: flex;
  211. align-items: center;
  212. flex-direction: column;
  213. justify-content: center;
  214. background-color: #fff;
  215. .head {
  216. width: 100%;
  217. }
  218. .color-picker {
  219. display: flex;
  220. align-items: center;
  221. flex-direction: column;
  222. justify-content: center;
  223. .color-name {
  224. margin: 23rpx;
  225. font-size: 45rpx;
  226. font-weight: bold;
  227. letter-spacing: 8rpx;
  228. }
  229. .show-view {
  230. height: 56rpx;
  231. width: 567rpx;
  232. }
  233. .hue-view {
  234. width: 567rpx;
  235. height: 56rpx;
  236. margin: 12rpx 0;
  237. position: relative;
  238. background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
  239. .anchor {
  240. width: 12rpx;
  241. height: 100%;
  242. position: absolute;
  243. background: #ffffff;
  244. transform: translate(-50%);
  245. box-shadow: 0 0 2rpx rgba(0, 0, 0, 0.6);
  246. }
  247. }
  248. .color-view {
  249. width: 567rpx;
  250. height: 345rpx;
  251. position: relative;
  252. margin-bottom: 12upx;
  253. &::before,
  254. &::after {
  255. content: '';
  256. top: 0;
  257. left: 0;
  258. width: 100%;
  259. height: 100%;
  260. position: absolute;
  261. }
  262. &::before {
  263. background: linear-gradient(to right, white, transparent);
  264. }
  265. &::after {
  266. background: linear-gradient(to top, black, transparent);
  267. }
  268. .anchor {
  269. z-index: 1;
  270. width: 24rpx;
  271. height: 24rpx;
  272. border-radius: 50%;
  273. position: absolute;
  274. border: 4rpx solid #ffffff;
  275. background: rgba(0, 0, 0, 0.3);
  276. transform: translate(-50%, -50%);
  277. }
  278. }
  279. }
  280. }
  281. </style>