index.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { BLUE, WHITE } from '../common/color';
  2. import { VantComponent } from '../common/component';
  3. import { getSystemInfoSync } from '../common/utils';
  4. import { isObj } from '../common/validator';
  5. import { canIUseCanvas2d } from '../common/version';
  6. import { adaptor } from './canvas';
  7. function format(rate) {
  8. return Math.min(Math.max(rate, 0), 100);
  9. }
  10. const PERIMETER = 2 * Math.PI;
  11. const BEGIN_ANGLE = -Math.PI / 2;
  12. const STEP = 1;
  13. VantComponent({
  14. props: {
  15. text: String,
  16. lineCap: {
  17. type: String,
  18. value: 'round',
  19. },
  20. value: {
  21. type: Number,
  22. value: 0,
  23. observer: 'reRender',
  24. },
  25. speed: {
  26. type: Number,
  27. value: 50,
  28. },
  29. size: {
  30. type: Number,
  31. value: 100,
  32. observer() {
  33. this.drawCircle(this.currentValue);
  34. },
  35. },
  36. fill: String,
  37. layerColor: {
  38. type: String,
  39. value: WHITE,
  40. },
  41. color: {
  42. type: null,
  43. value: BLUE,
  44. observer() {
  45. this.setHoverColor().then(() => {
  46. this.drawCircle(this.currentValue);
  47. });
  48. },
  49. },
  50. type: {
  51. type: String,
  52. value: '',
  53. },
  54. strokeWidth: {
  55. type: Number,
  56. value: 4,
  57. },
  58. clockwise: {
  59. type: Boolean,
  60. value: true,
  61. },
  62. },
  63. data: {
  64. hoverColor: BLUE,
  65. },
  66. methods: {
  67. getContext() {
  68. const { type, size } = this.data;
  69. if (type === '' || !canIUseCanvas2d()) {
  70. const ctx = wx.createCanvasContext('van-circle', this);
  71. return Promise.resolve(ctx);
  72. }
  73. const dpr = getSystemInfoSync().pixelRatio;
  74. return new Promise((resolve) => {
  75. wx.createSelectorQuery()
  76. .in(this)
  77. .select('#van-circle')
  78. .node()
  79. .exec((res) => {
  80. const canvas = res[0].node;
  81. const ctx = canvas.getContext(type);
  82. if (!this.inited) {
  83. this.inited = true;
  84. canvas.width = size * dpr;
  85. canvas.height = size * dpr;
  86. ctx.scale(dpr, dpr);
  87. }
  88. resolve(adaptor(ctx));
  89. });
  90. });
  91. },
  92. setHoverColor() {
  93. const { color, size } = this.data;
  94. if (isObj(color)) {
  95. return this.getContext().then((context) => {
  96. if (!context)
  97. return;
  98. const LinearColor = context.createLinearGradient(size, 0, 0, 0);
  99. Object.keys(color)
  100. .sort((a, b) => parseFloat(a) - parseFloat(b))
  101. .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, color[key]));
  102. this.hoverColor = LinearColor;
  103. });
  104. }
  105. this.hoverColor = color;
  106. return Promise.resolve();
  107. },
  108. presetCanvas(context, strokeStyle, beginAngle, endAngle, fill) {
  109. const { strokeWidth, lineCap, clockwise, size } = this.data;
  110. const position = size / 2;
  111. const radius = position - strokeWidth / 2;
  112. context.setStrokeStyle(strokeStyle);
  113. context.setLineWidth(strokeWidth);
  114. context.setLineCap(lineCap);
  115. context.beginPath();
  116. context.arc(position, position, radius, beginAngle, endAngle, !clockwise);
  117. context.stroke();
  118. if (fill) {
  119. context.setFillStyle(fill);
  120. context.fill();
  121. }
  122. },
  123. renderLayerCircle(context) {
  124. const { layerColor, fill } = this.data;
  125. this.presetCanvas(context, layerColor, 0, PERIMETER, fill);
  126. },
  127. renderHoverCircle(context, formatValue) {
  128. const { clockwise } = this.data;
  129. // 结束角度
  130. const progress = PERIMETER * (formatValue / 100);
  131. const endAngle = clockwise
  132. ? BEGIN_ANGLE + progress
  133. : 3 * Math.PI - (BEGIN_ANGLE + progress);
  134. this.presetCanvas(context, this.hoverColor, BEGIN_ANGLE, endAngle);
  135. },
  136. drawCircle(currentValue) {
  137. const { size } = this.data;
  138. this.getContext().then((context) => {
  139. if (!context)
  140. return;
  141. context.clearRect(0, 0, size, size);
  142. this.renderLayerCircle(context);
  143. const formatValue = format(currentValue);
  144. if (formatValue !== 0) {
  145. this.renderHoverCircle(context, formatValue);
  146. }
  147. context.draw();
  148. });
  149. },
  150. reRender() {
  151. // tofector 动画暂时没有想到好的解决方案
  152. const { value, speed } = this.data;
  153. if (speed <= 0 || speed > 1000) {
  154. this.drawCircle(value);
  155. return;
  156. }
  157. this.clearMockInterval();
  158. this.currentValue = this.currentValue || 0;
  159. const run = () => {
  160. this.interval = setTimeout(() => {
  161. if (this.currentValue !== value) {
  162. if (Math.abs(this.currentValue - value) < STEP) {
  163. this.currentValue = value;
  164. }
  165. else if (this.currentValue < value) {
  166. this.currentValue += STEP;
  167. }
  168. else {
  169. this.currentValue -= STEP;
  170. }
  171. this.drawCircle(this.currentValue);
  172. run();
  173. }
  174. else {
  175. this.clearMockInterval();
  176. }
  177. }, 1000 / speed);
  178. };
  179. run();
  180. },
  181. clearMockInterval() {
  182. if (this.interval) {
  183. clearTimeout(this.interval);
  184. this.interval = null;
  185. }
  186. },
  187. },
  188. mounted() {
  189. this.currentValue = this.data.value;
  190. this.setHoverColor().then(() => {
  191. this.drawCircle(this.currentValue);
  192. });
  193. },
  194. destroyed() {
  195. this.clearMockInterval();
  196. },
  197. });