index.vue 7.3 KB

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