ring_progress.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { MainColorType } from "@/context/themes/color";
  2. import { rpxToPx } from "@/utils/tools";
  3. import { Canvas } from "@tarojs/components";
  4. import Taro, { useReady } from "@tarojs/taro";
  5. import { useEffect, useRef, useState } from "react";
  6. export default function RingProgress(props: {
  7. canvasId?: string;
  8. bgRing: any;
  9. target?: any;
  10. real?: any;
  11. scale?: number;
  12. radius: number;
  13. width?: number;
  14. height?: number;
  15. extra?: any;
  16. count?: number;
  17. shareBg?: any;
  18. shareCover?: any;
  19. isCompleted?: boolean;
  20. }) {
  21. const canvasId = props.canvasId ?? new Date().getTime() + ''
  22. const info = Taro.getWindowInfo ? Taro.getWindowInfo() : Taro.getSystemInfoSync()
  23. const dpr = info.pixelRatio; // 获取设备的像素比
  24. const [ctx, setCtx] = useState<any>(null)
  25. const [canvas, setCanvas] = useState<any>(null)
  26. const canvasRef = useRef(null);
  27. const canvasWidth = props.width ?? rpxToPx(900)
  28. const canvasHeight = props.height ?? rpxToPx(720)
  29. const scale = props.scale ?? 1
  30. useEffect(() => {
  31. if (ctx) {
  32. drawContent(ctx, canvas)
  33. }
  34. else {
  35. initCanvas()
  36. }
  37. }, [props.canvasId, props.count])
  38. useReady(() => {
  39. initCanvas()
  40. })
  41. var retryCount = 0;
  42. function initCanvas() {
  43. const query = Taro.createSelectorQuery();
  44. query.select(`#${canvasId}`).fields({ node: true, size: true });
  45. query.exec((res) => {
  46. if (res[0] == null) {
  47. retryCount++;
  48. if (retryCount > 5) {
  49. return;
  50. }
  51. initCanvas()
  52. return
  53. }
  54. const _canvas = res[0].node;
  55. _canvas.width = res[0].width * dpr;
  56. _canvas.height = res[0].height * dpr;
  57. const ctx = _canvas.getContext('2d');
  58. ctx.scale(dpr, dpr)
  59. ctx.translate(0, 0)
  60. ctx.scale(scale, scale);
  61. ctx.translate(0, 0);
  62. drawContent(ctx, _canvas)
  63. setCtx(ctx)
  64. setCanvas(_canvas)
  65. })
  66. }
  67. function drawContent(ctx, _canvas) {
  68. ctx.clearRect(0, 0, canvasWidth, canvasHeight); // 清除画布
  69. // 设置画布尺寸
  70. if (props.shareBg) {
  71. if (props.shareBg.length == 1) {
  72. ctx.fillStyle = props.shareBg[0];
  73. ctx.fillRect(0, 0, canvasWidth, canvasHeight)
  74. }
  75. else {
  76. const grd = ctx.createLinearGradient(0, 0, 0, canvasHeight);
  77. grd.addColorStop(0, props.shareBg[0])
  78. grd.addColorStop(1, props.shareBg[1])
  79. ctx.fillStyle = grd;
  80. ctx.fillRect(0, 0, canvasWidth, canvasHeight)
  81. }
  82. }
  83. ctx.beginPath();
  84. ctx.arc(canvasWidth / 2.0, canvasHeight / 2.0, props.radius, 0, 2 * Math.PI);
  85. ctx.lineWidth = props.bgRing.width;
  86. ctx.strokeStyle = props.bgRing.color;
  87. ctx.lineCap = 'round'; // 设置为圆角
  88. ctx.stroke();
  89. if (props.target) {
  90. ctx.beginPath();
  91. ctx.arc(canvasWidth / 2.0, canvasHeight / 2.0, props.radius, props.target.start, props.target.start + Math.max(props.target.duration, 0.01));
  92. ctx.lineWidth = props.target.width;
  93. ctx.strokeStyle = props.target.color;
  94. ctx.lineCap = 'round'; // 设置为圆角
  95. ctx.stroke();
  96. }
  97. if (props.real) {
  98. if (props.isCompleted) {
  99. ctx.beginPath();
  100. ctx.arc(canvasWidth / 2.0, canvasHeight / 2.0, props.radius, 0, Math.PI*2);
  101. ctx.lineWidth = rpxToPx(4);
  102. ctx.strokeStyle = 'rgba(0,0,0,0.05)';
  103. ctx.lineCap = 'round'; // 设置为圆角
  104. ctx.stroke();
  105. }
  106. ctx.beginPath();
  107. ctx.arc(canvasWidth / 2.0, canvasHeight / 2.0, props.radius, props.real.start, props.real.start + Math.max(props.real.duration, 0.01));
  108. ctx.lineWidth = props.real.width;
  109. ctx.strokeStyle = props.real.color;
  110. ctx.lineCap = 'round'; // 设置为圆角
  111. ctx.stroke();
  112. }
  113. // ctx.beginPath()
  114. // ctx.arc(100, 100, 30, 0, 2 * Math.PI, false);
  115. // ctx.clip();
  116. // ctx.beginPath();
  117. // ctx.arc(100, 100, 30, 0, 2 * Math.PI, true);
  118. // ctx.fill();
  119. // 设置混合模式为擦除
  120. // ctx.globalCompositeOperation = 'destination-out'
  121. // // 绘制透明环
  122. // ctx.beginPath()
  123. // ctx.arc(100, 100, 50, 0, 2 * Math.PI)
  124. // ctx.lineWidth = 10
  125. // ctx.stroke()
  126. // // 重置混合模式
  127. // ctx.globalCompositeOperation = 'source-over'
  128. if (!props.isCompleted) {
  129. var time = new Date();
  130. var seconds = time.getHours() * 3600 + time.getMinutes() * 60 + time.getSeconds();
  131. // seconds += props.currentDot.offset! * 60
  132. if (seconds > 24 * 3600) {
  133. seconds -= 24 * 3600
  134. }
  135. else if (seconds < 0) {
  136. seconds += 24 * 3600
  137. }
  138. var arc = seconds / 86400 * 2 * Math.PI - Math.PI / 2.0;
  139. const radians = arc;//angle * Math.PI / 180; // 将角度转换为弧度
  140. const xPrime = canvasWidth / 2.0 + props.radius * Math.cos(radians);
  141. const yPrime = canvasHeight / 2.0 + props.radius * Math.sin(radians);
  142. ctx.globalCompositeOperation = 'destination-out'
  143. ctx.beginPath();
  144. var dotLineWidth = 4
  145. // if (lineWidth == 28) {
  146. // dotLineWidth = 4
  147. // }
  148. ctx.arc(xPrime, yPrime, props.target.width / 2.0, 0, 2 * Math.PI);
  149. ctx.lineWidth = dotLineWidth;
  150. ctx.fillStyle = 'transparent'
  151. ctx.fill()
  152. ctx.strokeStyle = '#1C1C1C';
  153. ctx.lineCap = 'round'; // 设置为圆角
  154. ctx.stroke();
  155. ctx.globalCompositeOperation = 'source-over'
  156. }
  157. if (props.extra) {
  158. const { goal, target, progress, value, footer, color, result } = props.extra
  159. // if (header) {
  160. // ctx.font = `bold ${12}px sans-serif`
  161. // ctx.fillStyle = '#000'
  162. // ctx.textAlign = 'center'
  163. // ctx.fillText(header, rpxToPx(450), rpxToPx(350));
  164. // }
  165. if (target) {
  166. ctx.font = `bold ${rpxToPx(50)}px sans-serif`
  167. ctx.fillStyle = '#000'
  168. ctx.textAlign = 'center'
  169. ctx.textBaseline = 'top';
  170. ctx.fillText(target, rpxToPx(450), rpxToPx(324));
  171. }
  172. if (progress) {
  173. ctx.font = `bold ${rpxToPx(72)}px sans-serif`
  174. ctx.fillStyle = '#000'
  175. ctx.textAlign = 'center'
  176. ctx.textBaseline = 'top';
  177. ctx.fillText(progress, rpxToPx(450), rpxToPx(314));
  178. }
  179. if (goal) {
  180. ctx.font = `bold ${rpxToPx(30)}px sans-serif`
  181. ctx.fillStyle = MainColorType.orange
  182. ctx.textAlign = 'center'
  183. ctx.textBaseline = 'top';
  184. ctx.fillText(goal, rpxToPx(450), rpxToPx(416));
  185. }
  186. if (footer) {
  187. ctx.font = `bold ${rpxToPx(24)}px sans-serif`
  188. ctx.fillStyle = 'rgba(0,0,0,0.5)'
  189. ctx.textAlign = 'center'
  190. ctx.textBaseline = 'top';
  191. ctx.fillText(footer, rpxToPx(450), rpxToPx(420));
  192. }
  193. if (result) {
  194. ctx.font = `bold ${rpxToPx(44)}px sans-serif`
  195. ctx.fillStyle = '#fff'
  196. ctx.textAlign = 'center'
  197. ctx.textBaseline = 'top';
  198. ctx.fillText(result, rpxToPx(450), rpxToPx(414));
  199. }
  200. }
  201. if (props.isCompleted) {
  202. if (_canvas) {
  203. const img1 = _canvas.createImage(); // 创建图像对象
  204. img1.src = global.checkImg
  205. img1.onload = () => {
  206. ctx.drawImage(img1, 162, 30, rpxToPx(400), rpxToPx(340));
  207. ctx.stroke();
  208. if (props.shareCover && _canvas) {
  209. save(_canvas)
  210. }
  211. }
  212. }
  213. }
  214. else {
  215. if (props.shareCover && _canvas) {
  216. save(_canvas)
  217. }
  218. }
  219. }
  220. function save(_canvas) {
  221. Taro.canvasToTempFilePath({
  222. canvas: _canvas,
  223. success: (res) => {
  224. console.log('图片保存成功:', res.tempFilePath);
  225. props.shareCover(res.tempFilePath)
  226. },
  227. fail: (err) => {
  228. console.error('转为图片失败:', err);
  229. }
  230. });
  231. }
  232. return <Canvas canvasId={canvasId} id={canvasId} className="canvas" type="2d"
  233. style={{ width: canvasWidth, height: canvasHeight, zIndex: 0 }}
  234. ref={canvasRef}
  235. />
  236. }