Rings.weapp.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import { MainColorType } from "@/context/themes/color";
  2. import { Canvas } from "@tarojs/components";
  3. import Taro from "@tarojs/taro";
  4. import { useDidShow, useReady } from "@tarojs/taro";
  5. import { useEffect, useRef, useState } from "react";
  6. import { useSelector } from "react-redux";
  7. import '../../../app.scss'
  8. export type RingCommon = {
  9. useCase: string;
  10. status: string;
  11. isFast?: boolean;
  12. radius: number;
  13. lineWidth: number;
  14. }
  15. export type CurrentDot = {
  16. color: string;
  17. lineWidth: number;
  18. borderColor: string;
  19. timestamp?: number;
  20. offset?: number;
  21. whiteIcon?: boolean;
  22. fillColor?:string;
  23. }
  24. export type RealRing = {
  25. color: string;
  26. startArc: number;
  27. durationArc: number;
  28. radius?: number;
  29. lineWidth?: number;
  30. hideBg?: boolean;
  31. }
  32. export type TargetRing = {
  33. color: string;
  34. startArc: number;
  35. durationArc: number;
  36. radius?: number;
  37. lineWidth?: number;
  38. }
  39. export type ScheduleRing = {
  40. color: string;
  41. startArc: number;
  42. durationArc: number;
  43. }
  44. export type BgRing = {
  45. color: string;
  46. }
  47. let arrowImg;
  48. let startTime;
  49. let dotScale = 0.8;
  50. let direction = 1;
  51. let frameAnimation: any = null;
  52. let laterTime;
  53. export default function Rings(props: {
  54. common: RingCommon;
  55. currentDot?: CurrentDot;
  56. realRing?: RealRing;
  57. targetRing?: TargetRing;
  58. scheduleRing?: ScheduleRing;
  59. breathAnimation?: boolean;
  60. bgRing: BgRing;
  61. canvasId?: string;
  62. ctx?: any;
  63. setCtx?: any;
  64. canvas?: any;
  65. setCanvas?: any;
  66. dotList?: Array<CurrentDot>;
  67. stageList?: Array<RealRing>;
  68. scale?: number;
  69. showCurrentDotAnimation?: boolean;
  70. }) {
  71. const r = props.common.radius
  72. const strokeWidth = props.common.lineWidth;
  73. // const color = props.color || 'orange'
  74. const canvasRef = useRef(null);
  75. const canvasId = props.canvasId ? 'canvas_' + props.canvasId : 'progress-canvas';
  76. const info = Taro.getWindowInfo?Taro.getWindowInfo():Taro.getSystemInfoSync()
  77. const dpr = info.pixelRatio; // 获取设备的像素比
  78. const radius = r; // 圆形进度条的半径
  79. const lineWidth = strokeWidth; // 圆形进度条的线宽
  80. const time = useSelector((state: any) => state.time);
  81. const user = useSelector((state: any) => state.user);
  82. const [scale, setScale] = useState(props.scale ?? 1.0)
  83. const animationFrameRef = useRef<number | null>(null);
  84. const animationDuration = 150; // 动画时长(毫秒
  85. const animateScale = (newScale) => {
  86. // const startTime = performance.now();
  87. const animate = () => {
  88. const elapsed = new Date().getTime() - startTime.getTime();
  89. const progress = Math.min(elapsed / animationDuration, 1);
  90. const currentScale = scale + (newScale - scale) * progress;
  91. setScale(currentScale);
  92. if (progress < 1) {
  93. requestAnimationFrame(animate);
  94. } else {
  95. setScale(newScale);
  96. }
  97. };
  98. requestAnimationFrame(animate);
  99. };
  100. useEffect(() => {
  101. // drawCircle()
  102. if (props.scale && props.scale != scale) {
  103. // setScale(props.scale ?? 1.0)
  104. startTime = new Date()
  105. animateScale(props.scale)
  106. }
  107. }, [props.scale])
  108. const animate2 = () => {
  109. if (!props.showCurrentDotAnimation) {
  110. cancelAnimationFrame(animationFrameRef.current as any)
  111. animationFrameRef.current = null
  112. return;
  113. }
  114. // 更新 scale 和 alpha
  115. dotScale += direction * 0.005;
  116. // 反转方向
  117. if (dotScale <= 0.7 || dotScale >= 1) {
  118. direction *= -1;
  119. }
  120. // 绘制圆点
  121. if (props.ctx) {
  122. drawContent(props.ctx)
  123. }
  124. else {
  125. drawCircle()
  126. }
  127. // 循环动画
  128. animationFrameRef.current = requestAnimationFrame(animate2);
  129. };
  130. useReady(() => {
  131. drawCircle()
  132. })
  133. useEffect(() => {
  134. if (props.ctx) {
  135. drawContent(props.ctx)
  136. }
  137. else {
  138. drawCircle()
  139. }
  140. }, [time.status, time.scenario, user.isLogin, props.targetRing, props.currentDot, props.realRing, props.currentDot?.color, props.canvasId]);
  141. var retryCount = 0;
  142. function drawCircle() {
  143. const query = Taro.createSelectorQuery();
  144. query.select(`#${canvasId}`).fields({ node: true, size: true });
  145. query.exec((res) => {
  146. if (res[0] == null) {
  147. retryCount++;
  148. if (retryCount > 5) {
  149. return;
  150. }
  151. drawCircle()
  152. // console.log(canvasId)
  153. return;
  154. }
  155. const _canvas = res[0].node;
  156. _canvas.width = res[0].width * dpr;
  157. _canvas.height = res[0].height * dpr;
  158. const ctx = _canvas.getContext('2d');
  159. global.canvas2 = _canvas
  160. if (props.setCtx) {
  161. props.setCtx(ctx)
  162. props.setCanvas(_canvas)
  163. drawContent(ctx)
  164. }
  165. else {
  166. drawContent(ctx)
  167. }
  168. // setCanvas(_canvas)
  169. // setContext(ctx)
  170. // const ctx = Taro.createCanvasContext(canvasId);
  171. // drawContent(ctx)
  172. });
  173. }
  174. function drawContent(ctx) {
  175. if (props.canvas) {
  176. props.canvas.width = ((radius * 2 + lineWidth)) * dpr;
  177. props.canvas.height = ((radius * 2 + lineWidth)) * dpr;
  178. }
  179. const center = radius + lineWidth / 2; // 圆心坐标
  180. ctx.clearRect(0, 0, radius * 2, radius * 2); // 清除画布
  181. // 设置画布尺寸
  182. ctx.scale(dpr, dpr)
  183. ctx.translate(center, 2 * center)
  184. ctx.scale(scale, scale);
  185. ctx.translate(-center, -2 * center);
  186. // 绘制背景圆
  187. ctx.beginPath();
  188. ctx.arc(center, center, radius, 0, 2 * Math.PI);
  189. ctx.lineWidth = lineWidth;
  190. ctx.strokeStyle = props.bgRing.color;
  191. ctx.lineCap = 'round'; // 设置为圆角
  192. ctx.stroke();
  193. // 绘制schedule进度环
  194. if (props.scheduleRing) {
  195. ctx.beginPath();
  196. ctx.arc(center, center, radius, props.scheduleRing!.startArc,
  197. props.scheduleRing!.startArc + props.scheduleRing!.durationArc);
  198. ctx.lineWidth = lineWidth + 4;
  199. ctx.strokeStyle = MainColorType.bg;
  200. ctx.lineCap = 'round'; // 设置为圆角
  201. ctx.stroke();
  202. ctx.beginPath();
  203. ctx.arc(center, center, radius, props.scheduleRing!.startArc,
  204. props.scheduleRing!.startArc + props.scheduleRing!.durationArc);
  205. ctx.lineWidth = lineWidth;
  206. ctx.strokeStyle = props.scheduleRing!.color;
  207. ctx.lineCap = 'round'; // 设置为圆角
  208. ctx.stroke();
  209. }
  210. // 绘制target进度环
  211. if (props.targetRing) {
  212. // ctx.beginPath();
  213. // ctx.arc(center, center, props.targetRing!.radius ? props.targetRing!.radius : radius, props.targetRing!.startArc,
  214. // props.targetRing!.startArc + props.targetRing!.durationArc);
  215. // ctx.lineWidth = props.targetRing!.lineWidth ? props.targetRing!.lineWidth + 4 : lineWidth + 4;
  216. // ctx.strokeStyle = MainColorType.bg;
  217. // ctx.lineCap = 'round'; // 设置为圆角
  218. // ctx.stroke();
  219. ctx.beginPath();
  220. ctx.arc(center, center, props.targetRing!.radius ? props.targetRing!.radius : radius, props.targetRing!.startArc,
  221. props.targetRing!.startArc + props.targetRing!.durationArc);
  222. ctx.lineWidth = props.targetRing!.lineWidth ? props.targetRing!.lineWidth : lineWidth;
  223. ctx.strokeStyle = props.targetRing!.color;
  224. ctx.lineCap = 'round'; // 设置为圆角
  225. ctx.stroke();
  226. }
  227. //绘制real进度环
  228. if (props.realRing) {
  229. if (props.realRing.durationArc < 0.01) props.realRing.durationArc = 0.01;
  230. // if (!props.realRing.hideBg) {
  231. // ctx.beginPath();
  232. // ctx.arc(center, center, props.realRing!.radius ? props.realRing!.radius : radius, props.realRing!.startArc,
  233. // props.realRing!.startArc + props.realRing!.durationArc);
  234. // ctx.lineWidth = props.realRing!.lineWidth ? props.realRing!.lineWidth + 4 : lineWidth + 4;
  235. // ctx.strokeStyle = MainColorType.bg;
  236. // ctx.lineCap = 'round'; // 设置为圆角
  237. // ctx.stroke();
  238. // }
  239. ctx.beginPath();
  240. ctx.arc(center, center, props.realRing!.radius ? props.realRing!.radius : radius, props.realRing!.startArc,
  241. props.realRing!.startArc + props.realRing!.durationArc);
  242. ctx.lineWidth = props.realRing!.lineWidth ? props.realRing!.lineWidth : lineWidth;
  243. ctx.strokeStyle = props.realRing!.color;
  244. ctx.lineCap = 'round'; // 设置为圆角
  245. ctx.stroke();
  246. }
  247. if (props.stageList) {
  248. props.stageList.map(item => {
  249. if (item.durationArc < 0.01) item.durationArc = 0.01;
  250. ctx.beginPath();
  251. ctx.arc(center, center, radius, item.startArc,
  252. item.startArc + item.durationArc);
  253. ctx.lineWidth = lineWidth;
  254. ctx.strokeStyle = item.color;
  255. ctx.lineCap = 'round'; // 设置为圆角
  256. ctx.stroke();
  257. })
  258. }
  259. //绘制current_dot点
  260. if (props.currentDot) {
  261. var time = new Date();
  262. var seconds = time.getHours() * 3600 + time.getMinutes() * 60 + time.getSeconds();
  263. seconds += props.currentDot.offset! * 60
  264. if (seconds > 24 * 3600) {
  265. seconds -= 24 * 3600
  266. }
  267. else if (seconds < 0) {
  268. seconds += 24 * 3600
  269. }
  270. var arc = seconds / 86400 * 2 * Math.PI - Math.PI / 2.0;
  271. const radians = arc;//angle * Math.PI / 180; // 将角度转换为弧度
  272. const xPrime = center + r * Math.cos(radians);
  273. const yPrime = center + r * Math.sin(radians);
  274. ctx.beginPath();
  275. var dotLineWidth = 2.5
  276. // if (lineWidth == 28) {
  277. // dotLineWidth = 4
  278. // }
  279. ctx.arc(xPrime, yPrime, lineWidth / 2.0 + dotLineWidth / 2-0.75, 0, 2 * Math.PI);
  280. ctx.lineWidth = dotLineWidth;
  281. ctx.fillStyle = props.currentDot.fillColor?props.currentDot.fillColor:'transparent'
  282. ctx.fill()
  283. ctx.strokeStyle = props.currentDot.borderColor//'#1C1C1C';
  284. ctx.lineCap = 'round'; // 设置为圆角
  285. ctx.stroke();
  286. // if (props.currentDot.color != '#ffffffff') {
  287. // ctx.beginPath();
  288. // // ctx.translate(xPrime, yPrime)
  289. // ctx.arc(xPrime, yPrime, (lineWidth-1) / 2 * dotScale, 0, 2 * Math.PI, false);
  290. // ctx.fillStyle = props.currentDot.color;
  291. // ctx.fill();
  292. // }
  293. // if (arrowImg) {
  294. // //绘制终点图标
  295. // ctx.save()
  296. // ctx.beginPath()
  297. // ctx.translate(xPrime, yPrime)
  298. // ctx.rotate(arc)
  299. // ctx.translate(-xPrime, -yPrime)
  300. // ctx.drawImage(arrowImg, xPrime - lineWidth / 2, yPrime - lineWidth / 2, lineWidth, lineWidth)
  301. // ctx.restore()
  302. // ctx.closePath()
  303. // }
  304. // else {
  305. // let tempImage = global.canvas2.createImage()
  306. // tempImage.src = require(props.currentDot.whiteIcon ? '@/assets/images/dot_arrow_white.png' : '@/assets/images/dot_arrow.png')//'./watch_line.png'//'..//assets/images/watch_line.png'
  307. // tempImage.onload = () => {
  308. // arrowImg = tempImage
  309. // ctx.save()
  310. // ctx.beginPath()
  311. // ctx.translate(xPrime, yPrime)
  312. // ctx.rotate(arc)
  313. // ctx.translate(-xPrime, -yPrime)
  314. // ctx.drawImage(arrowImg, xPrime - lineWidth / 2, yPrime - lineWidth / 2, lineWidth, lineWidth)
  315. // ctx.restore()
  316. // ctx.closePath()
  317. // }
  318. // }
  319. // ctx.beginPath();
  320. // ctx.arc(center, center, radius, arc,
  321. // arc + 0.001);
  322. // ctx.lineWidth = lineWidth+6;
  323. // ctx.strokeStyle = props.currentDot!.borderColor;
  324. // ctx.lineCap = 'round'; // 设置为圆角
  325. // ctx.stroke();
  326. // ctx.save()
  327. // ctx.beginPath();
  328. // ctx.arc(center, center, radius, arc,
  329. // arc + 0.001);
  330. // ctx.lineWidth = lineWidth;
  331. // ctx.strokeStyle = props.currentDot!.color;
  332. // ctx.lineCap = 'round'; // 设置为圆角
  333. // ctx.stroke();
  334. // ctx.restore()
  335. }
  336. if (props.dotList) {
  337. props.dotList.map(item => {
  338. var time = item.timestamp ? new Date(item.timestamp) : new Date();
  339. var seconds = time.getHours() * 3600 + time.getMinutes() * 60 + time.getSeconds();
  340. var arc = seconds / 86400 * 2 * Math.PI - Math.PI / 2.0;
  341. ctx.beginPath();
  342. ctx.arc(center, center, radius, arc,
  343. arc + 0.001);
  344. ctx.lineWidth = lineWidth + 6;
  345. ctx.strokeStyle = item.borderColor;
  346. ctx.lineCap = 'round'; // 设置为圆角
  347. ctx.stroke();
  348. ctx.save()
  349. ctx.beginPath();
  350. ctx.arc(center, center, radius, arc,
  351. arc + 0.001);
  352. ctx.lineWidth = lineWidth;
  353. ctx.strokeStyle = item.color;
  354. ctx.lineCap = 'round'; // 设置为圆角
  355. ctx.stroke();
  356. ctx.restore()
  357. })
  358. }
  359. }
  360. return <Canvas canvasId={canvasId} id={canvasId} className="canvas" type="2d" style={{ width: (radius * 2 + lineWidth), height: (radius * 2 + lineWidth), zIndex: 0 }} ref={canvasRef} />
  361. }