boide-round.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <view class="progress_box" :style="{ 'background-color': pageBg }">
  3. <!-- #ifdef MP-ALIPAY -->
  4. <canvas :id="id" :style="{ width: width + 'px', height: width + 'px' }" :disable-scroll="isMove"
  5. @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
  6. <!-- #endif -->
  7. <!-- #ifndef MP-ALIPAY -->
  8. <canvas :canvas-id="id" :style="{ width: width + 'px', height: width + 'px' }" :disable-scroll="isMove"
  9. @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
  10. <!-- #endif -->
  11. <view class="p_ps_show" v-if="!$slots.default">
  12. <view class="ps_text plan" :style="{'color': colorEnd}">
  13. <text v-if="false">{{circleTotal}}</text>
  14. <text>{{centerTxt}}</text>
  15. </view>
  16. <!-- <view class="ps_text his" v-if="isTwoVal">
  17. <text>{{twoVal}}</text>
  18. <text>Puffs</text>
  19. </view> -->
  20. </view>
  21. <view v-else class="p_ps_show">
  22. <slot></slot>
  23. </view>
  24. </view>
  25. </template>
  26. <script>
  27. function isInArea(e, r) {
  28. return Math.pow(e.x - r.x, 2) + Math.pow(e.y - r.y, 2) <= Math.pow(r.r + 10, 2);
  29. }
  30. function isInCtrl(e, r) {
  31. if (isInArea(e, r)) {
  32. return true;
  33. } else {
  34. return false;
  35. }
  36. }
  37. export default {
  38. props: {
  39. id: {
  40. default: 'canvas'
  41. },
  42. val: {
  43. default: 0
  44. },
  45. twoVal: {
  46. default: 0
  47. },
  48. isTwoVal: {
  49. default: true
  50. },
  51. // 步进值
  52. step: {
  53. default: 5
  54. },
  55. // 宽度
  56. width: {
  57. default: 220
  58. },
  59. // 圆的宽度
  60. border: {
  61. default: 35
  62. },
  63. colorSatrt: {
  64. default: '#FFD2E0'
  65. },
  66. // 圆的颜色
  67. colorEnd: {
  68. default: '#ff0163'
  69. },
  70. // 圆的外层的颜色
  71. colorButton: {
  72. default: '#ffffff'
  73. },
  74. // 背景色
  75. pageBg: {
  76. default: '#ffffff'
  77. },
  78. // 一圈的值
  79. circle: {
  80. default: 300
  81. },
  82. oneCircle: {
  83. default: false
  84. }
  85. },
  86. data() {
  87. return {
  88. oldTouches: {},
  89. valData: 0,
  90. circleNum: 0,
  91. centerColor: '',
  92. valPoint: {},
  93. centerPoint: {},
  94. isMove: false,
  95. centerTxt:'滑动解锁'
  96. };
  97. },
  98. mounted() {
  99. this.circleNum = Math.floor(this.val / this.circle)
  100. this.valData = this.val % this.circle
  101. const gradientList = this.gradient(this.colorSatrt, this.colorEnd, 3)
  102. this.centerColor = gradientList ? gradientList[1] : '#ff8cb6'
  103. this.setDrawCircle(this.valData)
  104. },
  105. computed: {
  106. circleTotal() {
  107. return this.circleNum * this.circle + this.valData
  108. }
  109. },
  110. watch: {
  111. val(val) {
  112. this.circleNum = Math.floor(val / this.circle)
  113. this.valData = val % this.circle
  114. this.setDrawCircle(this.valData)
  115. }
  116. },
  117. methods: {
  118. setDrawCircle(val) {
  119. this.valData = val
  120. this.drawCircle(this.valData, this.width, this.border, this.colorSatrt, this.colorEnd, this.colorButton);
  121. },
  122. drawCircle(val = 0, width, border, colorSatrt, colorEnd, colorButton) {
  123. let radius = width / 2 - 20; //半径
  124. let centerPoint = {
  125. //坐标
  126. x: width / 2,
  127. y: width / 2,
  128. r: radius
  129. };
  130. this.centerPoint = centerPoint;
  131. let ctx = uni.createCanvasContext(this.id, this);
  132. if (this.circleNum <= 0) {
  133. // 圆的颜色
  134. ctx.setStrokeStyle(colorSatrt);
  135. // 开始绘制圆形
  136. ctx.beginPath();
  137. // 设置圆的宽度
  138. ctx.setLineWidth(border + 4);
  139. // 绘制一个圆形
  140. ctx.arc(centerPoint.x, centerPoint.y, radius, 0, 2 * Math.PI, false);
  141. ctx.stroke();
  142. ctx.closePath();
  143. }
  144. const circleb = (this.circle / 2)
  145. const overOneRound = this.circleNum >= 1
  146. let arcVal = val
  147. let orign = [centerPoint.x, centerPoint.y]
  148. if (overOneRound) {
  149. arcVal = this.circle
  150. ctx.save()
  151. ctx.translate(centerPoint.x, centerPoint.y)
  152. ctx.rotate(val / this.circle * 2 * Math.PI)
  153. orign = [0, 0]
  154. }
  155. // 绘制渐变
  156. ctx.beginPath();
  157. // 设置圆的宽度
  158. ctx.setLineWidth(border + 4);
  159. ctx.setLineCap('round')
  160. let b = ctx.createLinearGradient(0, 0, 0, orign[1] + radius); //创建渐变对象  渐变开始点和渐变结束点
  161. b.addColorStop(0, colorSatrt); //渐变开始值
  162. b.addColorStop(1, this.centerColor); //渐变结束值
  163. ctx.strokeStyle = b; //使用渐变对象作为圆环的颜色
  164. ctx.arc(orign[0], orign[1], radius, 1.5 * Math.PI, (1.5 + arcVal / circleb) * Math.PI, false);
  165. ctx.stroke();
  166. ctx.closePath();
  167. if (arcVal > circleb) {
  168. ctx.beginPath();
  169. ctx.setLineCap('round')
  170. ctx.setLineWidth(border + 4);
  171. let g = ctx.createLinearGradient(0, orign[1] + radius, 0, 0);
  172. g.addColorStop(0, this.centerColor);
  173. g.addColorStop(1, colorEnd);
  174. ctx.strokeStyle = g;
  175. ctx.arc(orign[0], orign[1], radius, 0.5 * Math.PI, (0.5 + (arcVal - circleb) / circleb) * Math.PI,
  176. false);
  177. ctx.stroke();
  178. ctx.closePath();
  179. }
  180. if (overOneRound) {
  181. ctx.restore()
  182. }
  183. //画控制点
  184. let valRadius = border + 1; //点的半径
  185. // 点的半径
  186. let valRadian = 0;
  187. val -= circleb - circleb / 2
  188. let valPoint = {
  189. x: centerPoint.x + radius * Math.cos((val / circleb) * Math.PI),
  190. y: centerPoint.y + radius * Math.sin((val / circleb) * Math.PI),
  191. r: valRadius,
  192. v: val,
  193. s: 0.75 + valRadian,
  194. e: 2.25 - valRadian,
  195. t: 1.5 - 2 * valRadian,
  196. n: val
  197. };
  198. // console.log(valPoint)
  199. // console.log('centerPoint',centerPoint)
  200. this.valPoint = valPoint;
  201. // 点最外层
  202. ctx.beginPath();
  203. ctx.setFillStyle(colorButton);
  204. ctx.arc(valPoint.x, valPoint.y, valPoint.r, 0, 2 * Math.PI, false);
  205. ctx.fill()
  206. // 点内部的颜色
  207. ctx.beginPath();
  208. ctx.setFillStyle(colorEnd);
  209. ctx.arc(valPoint.x, valPoint.y, valPoint.r - 2, 0, 2 * Math.PI, false);
  210. ctx.fill();
  211. ctx.closePath();
  212. // 绘制箭头
  213. ctx.translate(valPoint.x, valPoint.y)
  214. ctx.rotate(val / this.circle * 2 * Math.PI)
  215. ctx.beginPath();
  216. ctx.setLineCap('round')
  217. ctx.setLineWidth(3)
  218. ctx.setStrokeStyle('#FF99C1')
  219. ctx.moveTo(0, 4);
  220. ctx.lineTo(-5, -2);
  221. ctx.stroke()
  222. ctx.moveTo(0, 4);
  223. ctx.lineTo(5, -2);
  224. ctx.stroke()
  225. ctx.closePath();
  226. ctx.setTransform(1, 0, 0, 1, 0, 0)
  227. ctx.draw();
  228. },
  229. // 获取两个颜色之间渐变的色值
  230. gradient(startColor, endColor, step) {
  231. function rgbToHex(r, g, b) {
  232. let hex = ((r << 16) | (g << 8) | b).toString(16);
  233. return "#" + new Array(Math.abs(hex.length - 7)).join("0") + hex;
  234. }
  235. function hexToRgb(hex) {
  236. let rgb = [];
  237. for (let i = 1; i < 7; i += 2) {
  238. rgb.push(parseInt("0x" + hex.slice(i, i + 2)));
  239. }
  240. return rgb;
  241. }
  242. //将hex转换为rgb
  243. let sColor = hexToRgb(startColor);
  244. let eColor = hexToRgb(endColor);
  245. //计算R\G\B每一步的差值
  246. let rStep = (eColor[0] - sColor[0]) / step;
  247. let gStep = (eColor[1] - sColor[1]) / step;
  248. let bStep = (eColor[2] - sColor[2]) / step;
  249. let gradientColorArr = [];
  250. for (let i = 0; i < step; i++) {
  251. //计算每一步的hex值
  252. gradientColorArr.push(rgbToHex(parseInt(rStep * i + sColor[0]), parseInt(gStep * i + sColor[1]),
  253. parseInt(bStep * i + sColor[2])));
  254. }
  255. return gradientColorArr;
  256. },
  257. touchStart(e) {
  258. let touches = e.mp.changedTouches[0] || e.changedTouches[0];
  259. this.oldTouches = {
  260. x: touches.x,
  261. y: touches.y
  262. };
  263. if (isInCtrl(touches, this.valPoint)) {
  264. this.isMove = true;
  265. }
  266. },
  267. touchMove(e) {
  268. const oldTouches = this.oldTouches;
  269. const touches = e.mp.changedTouches[0] || e.changedTouches[0];
  270. if (this.isMove === true) {
  271. let angle = Math.atan2(this.centerPoint.y - touches.y, this.centerPoint.x - touches.x) / Math.PI;
  272. let degrees = angle * 180 - 90 // -180 ~ 180
  273. // 判断是加还是减
  274. let numVal = 0
  275. if (degrees >= 0 && degrees <= 180) {
  276. numVal = Math.round(degrees * ((this.circle / 2) / 180))
  277. } else {
  278. numVal = Math.round((360 + degrees) * ((this.circle / 2) / 180))
  279. }
  280. if (numVal < this.circle / 30 && this.valData > this.circle - this.circle / 30) {
  281. this.circleNum += 1
  282. } else if ((numVal - this.valData) >= (this.circle / 2)) {
  283. this.circleNum -= 1
  284. }
  285. this.valData = numVal
  286. if (this.circleNum < 0) {
  287. this.circleNum = 0
  288. this.valData = 0
  289. }
  290. if (this.oneCircle && this.circleNum > 0) {
  291. this.circleNum = 1
  292. this.valData = 0
  293. }
  294. this.setDrawCircle(this.valData)
  295. }
  296. },
  297. touchEnd(e) {
  298. if (this.isMove) {
  299. this.$emit('change', this.circleTotal);
  300. if(this.circleTotal < 290) {
  301. this.centerTxt = '解锁失败';
  302. let that = this;
  303. setTimeout(function() {
  304. that.centerTxt = '滑动解锁';
  305. that.setDrawCircle(0)
  306. },500);
  307. }else {
  308. this.centerTxt = '解锁成功';
  309. }
  310. }
  311. this.isMove = false;
  312. }
  313. }
  314. };
  315. </script>
  316. <style scoped lang="scss">
  317. .progress_box {
  318. position: relative;
  319. .p_ps_show {
  320. position: absolute;
  321. top: 50%;
  322. left: 50%;
  323. transform: translate(-50%, -50%);
  324. display: flex;
  325. flex-direction: column;
  326. align-items: center;
  327. .ps_text {
  328. display: flex;
  329. flex-direction: column;
  330. align-items: center;
  331. font-size: 60rpx;
  332. padding: 0 20rpx;
  333. &.plan {
  334. border-bottom: solid 0rpx #acacac;
  335. padding-bottom: 8rpx;
  336. }
  337. &.his {
  338. color: #acacac;
  339. }
  340. text {
  341. &:first-child {
  342. font-weight: bold;
  343. }
  344. &:last-child {
  345. font-size: 30rpx;
  346. }
  347. }
  348. }
  349. }
  350. }
  351. </style>