index.vue 5.8 KB

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