diff --git a/example/app.json b/example/app.json index 7ed69795..5e78ac7a 100644 --- a/example/app.json +++ b/example/app.json @@ -53,7 +53,8 @@ "pages/datetime-picker/index", "pages/rate/index", "pages/collapse/index", - "pages/picker/index" + "pages/picker/index", + "pages/circle/index" ], "window": { "navigationBarBackgroundColor": "#f8f8f8", @@ -111,7 +112,8 @@ "van-rate": "./dist/rate/index", "van-collapse": "./dist/collapse/index", "van-collapse-item": "./dist/collapse-item/index", - "van-picker": "./dist/picker/index" + "van-picker": "./dist/picker/index", + "van-circle": "./dist/circle/index" }, "sitemapLocation": "sitemap.json" } diff --git a/example/config.js b/example/config.js index dcada1c1..41fbc642 100644 --- a/example/config.js +++ b/example/config.js @@ -109,6 +109,10 @@ export default [ groupName: '展示组件', icon: 'photo-o', list: [ + { + path: '/circle', + title: 'Circle 进度条' + }, { path: '/collapse', title: 'Collapse 折叠面板' diff --git a/example/pages/circle/index.js b/example/pages/circle/index.js new file mode 100644 index 00000000..1828d203 --- /dev/null +++ b/example/pages/circle/index.js @@ -0,0 +1,19 @@ +import Page from '../../common/page'; +const format = rate => Math.min(Math.max(rate, 0), 100); + +Page({ + data: { + value: 25, + gradientColor: { + '0%': '#ffd01e', + '100%': '#ee0a24' + } + }, + + run(e) { + const step = parseFloat(e.currentTarget.dataset.step); + this.setData({ + value: format((this.data.value += step)) + }); + } +}); diff --git a/example/pages/circle/index.json b/example/pages/circle/index.json new file mode 100644 index 00000000..052e143c --- /dev/null +++ b/example/pages/circle/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "Circle 进度条" +} diff --git a/example/pages/circle/index.wxml b/example/pages/circle/index.wxml new file mode 100644 index 00000000..a1175caf --- /dev/null +++ b/example/pages/circle/index.wxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + +增加 +减少 diff --git a/example/pages/circle/index.wxss b/example/pages/circle/index.wxss new file mode 100644 index 00000000..918284fb --- /dev/null +++ b/example/pages/circle/index.wxss @@ -0,0 +1,6 @@ +.van-circle { + margin: 5px 10px 20px; +} +.van-button{ + margin-left: 10px; +} diff --git a/packages/circle/README.md b/packages/circle/README.md new file mode 100644 index 00000000..41b26746 --- /dev/null +++ b/packages/circle/README.md @@ -0,0 +1,105 @@ +# Circle 环形进度条 + +### 引入 + +在`app.json`或`index.json`中引入组件,默认为`ES6`版本,`ES5`引入方式参见[快速上手](#/quickstart) + +```json +"usingComponents": { + "van-circle": "path/to/vant-weapp/dist/circle/index" +} +``` + +## 代码演示 + +### 基础用法 + +`value`属性表示进度条的目标进度。 + +```html + +``` + +### 宽度定制 + +通过`stroke-width`属性来控制进度条宽度 + +```html + +``` + +### 颜色定制 + +通过`color`属性来控制进度条颜色,`layer-color`属性来控制轨道颜色 + +```html + +``` + +### 渐变色 + +`color`属性支持传入对象格式来定义渐变色 + +```html + +``` + +```javascript +Page({ + data: { + value: 25, + gradientColor: { + '0%': '#ffd01e', + '100%': '#ee0a24' + } + } +}); +``` + +### 逆时针方向 + +将`clockwise`设置为`false`,进度会从逆时针方向开始 + +```html + +``` + +### 大小定制 + +通过`size`属性设置圆环直径 + +```html + +``` + +## API + +### Props + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| ------------ | -------------------------------------- | ------------------ | --------- | ---- | +| value | 目标进度 | *number* | `0` | - | +| size | 圆环直径,默认单位为 `px` | *string \| number* | `100` | - | +| color | 进度条颜色,传入对象格式可以定义渐变色 | *string \| object* | `#1989fa` | - | +| layer-color | 轨道颜色 | *string* | `#fff` | - | +| fill | 填充颜色 | *string* | - | - | +| speed | 动画速度(单位为 value/s) | *number* | `50` | - | +| text | 文字 | *string* | - | - | +| stroke-width | 进度条宽度 | *number* | `4` | - | +| clockwise | 是否顺时针增加 | *boolean* | `true` | - | + +### Slots + +| 名称 | 说明 | +| ------- | -------------- | +| default | 自定义文字内容,如果设置了`fill`,插槽内容会被原生组件覆盖 | diff --git a/packages/circle/index.json b/packages/circle/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/packages/circle/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/packages/circle/index.less b/packages/circle/index.less new file mode 100644 index 00000000..c2c94847 --- /dev/null +++ b/packages/circle/index.less @@ -0,0 +1,16 @@ +@import '../common/style/var.less'; + +.van-circle { + position: relative; + display: inline-block; + text-align: center; + + &__text { + position: absolute; + color: @circle-text-color; + top: 50%; + left: 0; + width: 100%; + transform: translateY(-50%); + } +} diff --git a/packages/circle/index.ts b/packages/circle/index.ts new file mode 100644 index 00000000..08a859b5 --- /dev/null +++ b/packages/circle/index.ts @@ -0,0 +1,185 @@ +import { VantComponent } from '../common/component'; +import { isObj } from '../common/utils'; +import { BLUE, WHITE } from '../common/color'; + +function format(rate) { + return Math.min(Math.max(rate, 0), 100); +} +const PERIMETER = 2 * Math.PI; +const BEGIN_ANGLE = -Math.PI / 2; +const STEP = 1; + +VantComponent({ + props: { + text: String, + lineCap: { + type: String, + value: 'round' + }, + value: { + type: Number, + value: 0, + observer: 'reRender' + }, + speed: { + type: Number, + value: 50 + }, + size: { + type: Number, + value: 100, + observer: 'setStyle' + }, + fill: String, + layerColor: { + type: String, + value: WHITE + }, + color: { + type: [String, Object], + value: BLUE, + observer: 'setHoverColor' + }, + strokeWidth: { + type: Number, + value: 4 + }, + clockwise: { + type: Boolean, + value: true + } + }, + + data: { + style: 'width: 100px; height: 100px;', + hoverColor: BLUE + }, + + methods: { + getContext() { + return ( + this.ctx || (this.ctx = wx.createCanvasContext('van-circle', this)) + ); + }, + + setHoverColor() { + const context = this.getContext(); + const { color, size } = this.data; + let hoverColor = color; + + if (isObj(color)) { + const LinearColor = context.createLinearGradient(size, 0, 0, 0); + Object.keys(color) + .sort((a, b) => parseFloat(a) - parseFloat(b)) + .map(key => { + LinearColor.addColorStop(parseFloat(key) / 100, color[key]); + }); + hoverColor = LinearColor; + } + + this.set({ + hoverColor + }); + }, + + setStyle() { + const { size } = this.data; + const style = `width: ${size}px; height: ${size}px;`; + + this.set({ + style + }); + }, + + presetCanvas(context, strokeStyle, beginAngle, endAngle, fill) { + const { strokeWidth, lineCap, clockwise, size } = this.data; + const position = size / 2; + const radius = position - strokeWidth / 2; + + context.setStrokeStyle(strokeStyle); + context.setLineWidth(strokeWidth); + context.setLineCap(lineCap); + context.beginPath(); + context.arc(position, position, radius, beginAngle, endAngle, !clockwise); + context.stroke(); + + if (fill) { + context.setFillStyle(fill); + context.fill(); + } + }, + + renderLayerCircle(context) { + const { layerColor, fill } = this.data; + this.presetCanvas(context, layerColor, 0, PERIMETER, fill); + }, + + renderHoverCircle(context, formatValue) { + const { clockwise, hoverColor } = this.data; + // 结束角度 + const progress = PERIMETER * (formatValue / 100); + const endAngle = clockwise + ? BEGIN_ANGLE + progress + : 3 * Math.PI - (BEGIN_ANGLE + progress); + + this.presetCanvas(context, hoverColor, BEGIN_ANGLE, endAngle); + }, + + drawCircle(currentValue) { + const context = this.getContext(); + const { size } = this.data; + context.clearRect(0, 0, size, size); + this.renderLayerCircle(context); + + const formatValue = format(currentValue); + if (formatValue !== 0) { + this.renderHoverCircle(context, formatValue); + } + + context.draw(); + }, + + reRender() { + // tofector 动画暂时没有想到好的解决方案 + const { value, speed } = this.data; + + if (speed <= 0 || speed > 1000) { + this.drawCircle(value); + return; + } + + this.clearInterval(); + this.currentValue = this.currentValue || 0; + this.interval = setInterval(() => { + if (this.currentValue !== value) { + if (this.currentValue < value) { + this.currentValue += STEP; + } else { + this.currentValue -= STEP; + } + this.drawCircle(this.currentValue); + } else { + this.clearInterval(); + } + }, 1000 / speed); + }, + + clearInterval() { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } + }, + + created() { + const { value } = this.data; + this.currentValue = value; + this.drawCircle(value); + }, + + destroyed() { + this.ctx = null; + this.clearInterval(); + } +}); diff --git a/packages/circle/index.wxml b/packages/circle/index.wxml new file mode 100644 index 00000000..215122ff --- /dev/null +++ b/packages/circle/index.wxml @@ -0,0 +1,7 @@ + + + + + + {{ text }} + \ No newline at end of file diff --git a/packages/common/style/var.less b/packages/common/style/var.less index c26e6a74..5ebd8aff 100644 --- a/packages/common/style/var.less +++ b/packages/common/style/var.less @@ -157,3 +157,6 @@ // Rate @rate-horizontal-padding: 2px; + +// Circle +@circle-text-color: @text-color;