From b7244f8fa9bb0a053111382352d6c5cda7ad8fd2 Mon Sep 17 00:00:00 2001 From: neverland Date: Tue, 20 Aug 2019 10:27:14 +0800 Subject: [PATCH] [new feature] Circle: support gradient color (#4157) --- src/circle/README.md | 70 ++++++++++-- src/circle/README.zh-CN.md | 80 ++++++++++++-- src/circle/demo/index.vue | 100 +++++++++++++----- src/circle/index.js | 62 +++++++++-- src/circle/index.less | 3 - .../test/__snapshots__/demo.spec.js.snap | 48 +++++++-- .../test/__snapshots__/index.spec.js.snap | 8 ++ src/circle/test/index.spec.js | 39 +++++++ 8 files changed, 346 insertions(+), 64 deletions(-) create mode 100644 src/circle/test/__snapshots__/index.spec.js.snap create mode 100644 src/circle/test/index.spec.js diff --git a/src/circle/README.md b/src/circle/README.md index 52c136f8a..896a0e108 100644 --- a/src/circle/README.md +++ b/src/circle/README.md @@ -37,20 +37,72 @@ export default { }; ``` -### Custom style +### Custom Width ```html +``` + +### Custom Color + +```html + +``` + +### Gradient + +```html + +``` + +``` javascript +export default { + data() { + return { + currentRate: 0, + gradientColor: { + '0%': '#ffd01e', + '100%': '#ee0a24' + } + }; + } +}; +``` + +### Counter Clockwise + +```html + +``` + +### Custom Size + +```html + ``` @@ -63,7 +115,7 @@ export default { | v-model | Current rate | `number` | - | | rate | Target rate | `number` | `100` | | size | Circle size | `string` | `100px` | -| color | Progress bar color | `string` | `#1989fa` | +| color | Progress color, passing object to render gradient | `string | object` | `#1989fa` | | layer-color | Layer color | `string` | `#fff` | | fill | Fill color | `string` | `none` | | speed | Animate speed(rate/s)| `number` | `0` | diff --git a/src/circle/README.zh-CN.md b/src/circle/README.zh-CN.md index 4ae7e52a5..abd671fb7 100644 --- a/src/circle/README.zh-CN.md +++ b/src/circle/README.zh-CN.md @@ -39,20 +39,82 @@ export default { }; ``` -### 样式定制 +### 宽度定制 + +通过`stroke-width`属性来控制进度条宽度 ```html +``` + +### 颜色定制 + +通过`color`属性来控制进度条颜色,`layer-color`属性来控制轨道颜色 + +```html + +``` + +### 渐变色 + +`color`属性支持传入对象格式来定义渐变色 + +```html + +``` + +``` javascript +export default { + data() { + return { + currentRate: 0, + gradientColor: { + '0%': '#ffd01e', + '100%': '#ee0a24' + } + }; + } +}; +``` + +### 逆时针方向 + +将`clockwise`设置为`false`,进度会从逆时针方向开始 + +```html + +``` + +### 大小定制 + +通过`size`属性设置圆环直径 + +```html + ``` @@ -65,7 +127,7 @@ export default { | v-model | 当前进度 | `number` | - | - | | rate | 目标进度 | `number` | `100` | - | | size | 圆环直径 | `string` | `100px` | - | -| color | 进度条颜色 | `string` | `#1989fa` | - | +| color | 进度条颜色,传入对象格式可以定义渐变色 | `string | object` | `#1989fa` | 2.1.4 | | layer-color | 轨道颜色 | `string` | `#fff` | - | | fill | 填充颜色 | `string` | `none` | - | | speed | 动画速度(单位为 rate/s)| `number` | `0` | - | diff --git a/src/circle/demo/index.vue b/src/circle/demo/index.vue index 9102c73ca..bc39e255b 100644 --- a/src/circle/demo/index.vue +++ b/src/circle/demo/index.vue @@ -5,39 +5,73 @@ v-model="currentRate1" :rate="rate" :speed="100" - size="120px" :text="currentRate1.toFixed(0) + '%'" /> -
- - -
+ + + + + + + + + +
+ + +
@@ -47,18 +81,34 @@ const format = rate => Math.min(Math.max(rate, 0), 100); export default { i18n: { 'zh-CN': { - customStyle: '样式定制' + gradient: '渐变色', + customSize: '大小定制', + customStyle: '样式定制', + customColor: '颜色定制', + customWidth: '宽度定制', + counterClockwise: '逆时针' }, 'en-US': { - customStyle: 'Custom Style' + gradient: 'Gradient', + customSize: 'Custom Size', + customStyle: 'Custom Style', + customColor: 'Custom Color', + customWidth: 'Custom Width', + counterClockwise: 'Counter Clockwise' } }, data() { return { - rate: 30, - currentRate1: 30, - currentRate2: 30 + rate: 70, + currentRate1: 70, + currentRate2: 70, + currentRate3: 70, + currentRate4: 70, + gradientColor: { + '0%': '#ffd01e', + '100%': '#ee0a24' + } }; }, diff --git a/src/circle/index.js b/src/circle/index.js index e8b2a0366..f3bab8f13 100644 --- a/src/circle/index.js +++ b/src/circle/index.js @@ -1,15 +1,22 @@ -import { createNamespace } from '../utils'; +import { createNamespace, isObj } from '../utils'; import { raf, cancelRaf } from '../utils/dom/raf'; import { BLUE, WHITE } from '../utils/constant'; const [createComponent, bem] = createNamespace('circle'); + const PERIMETER = 3140; -const PATH = 'M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0'; + +let uid = 0; function format(rate) { return Math.min(Math.max(rate, 0), 100); } +function getPath(clockwise) { + const sweepFlag = clockwise ? 1 : 0; + return `M 530 530 m 0, -500 a 500, 500 0 1, ${sweepFlag} 0, 1000 a 500, 500 0 1, ${sweepFlag} 0, -1000`; +} + export default createComponent({ props: { text: String, @@ -38,7 +45,7 @@ export default createComponent({ default: WHITE }, color: { - type: String, + type: [String, Object], default: BLUE }, strokeWidth: { @@ -51,6 +58,10 @@ export default createComponent({ } }, + beforeCreate() { + this.uid = `van-circle-gradient-${uid++}`; + }, + computed: { style() { return { @@ -59,14 +70,17 @@ export default createComponent({ }; }, + path() { + return getPath(this.clockwise); + }, + layerStyle() { - let offset = (PERIMETER * (100 - this.value)) / 100; - offset = this.clockwise ? offset : PERIMETER * 2 - offset; + const offset = (PERIMETER * this.value) / 100; return { stroke: `${this.color}`, - strokeDashoffset: `${offset}px`, - strokeWidth: `${this.strokeWidth + 1}px` + strokeWidth: `${this.strokeWidth + 1}px`, + strokeDasharray: `${offset}px ${PERIMETER}px` }; }, @@ -76,6 +90,30 @@ export default createComponent({ stroke: `${this.layerColor}`, strokeWidth: `${this.strokeWidth}px` }; + }, + + gradient() { + return isObj(this.color); + }, + + LinearGradient() { + if (!this.gradient) { + return; + } + + const Stops = Object.keys(this.color) + .sort((a, b) => parseFloat(a) - parseFloat(b)) + .map((key, index) => ( + + )); + + return ( + + + {Stops} + + + ); } }, @@ -116,8 +154,14 @@ export default createComponent({ return (
- - + {this.LinearGradient} + + {this.slots() || (this.text &&
{this.text}
)}
diff --git a/src/circle/index.less b/src/circle/index.less index 83fcdcfcb..65d0c9020 100644 --- a/src/circle/index.less +++ b/src/circle/index.less @@ -14,14 +14,11 @@ } &__layer { - transform: rotate(90deg); // should not use transform-origin: center // that will cause incorrect style in android devices transform-origin: 530px 530px; fill: none; stroke-linecap: round; - stroke-dasharray: 3140; - stroke-dashoffset: 3140; } &__text { diff --git a/src/circle/test/__snapshots__/demo.spec.js.snap b/src/circle/test/__snapshots__/demo.spec.js.snap index aee6974a5..c76bf29ab 100644 --- a/src/circle/test/__snapshots__/demo.spec.js.snap +++ b/src/circle/test/__snapshots__/demo.spec.js.snap @@ -3,21 +3,51 @@ exports[`renders demo correctly 1`] = `
-
- - +
+ + -
30%
+
70%
-
-
- - +
+ + -
30%
+
宽度定制
+
+
+ + + +
颜色定制
+
+
+ + + + + + + + + +
渐变色
+
+
+ + + +
逆时针
+
+
+ + + +
大小定制
+
`; diff --git a/src/circle/test/__snapshots__/index.spec.js.snap b/src/circle/test/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..d3e0afa57 --- /dev/null +++ b/src/circle/test/__snapshots__/index.spec.js.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`speed is 0 1`] = ` +
+ + +
+`; diff --git a/src/circle/test/index.spec.js b/src/circle/test/index.spec.js new file mode 100644 index 000000000..75465d5c6 --- /dev/null +++ b/src/circle/test/index.spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import Circle from '..'; +import { mount, later } from '../../../test/utils'; + +test('speed is 0', async () => { + const wrapper = mount(Circle, { + propsData: { + rate: 50, + value: 0 + }, + listeners: { + input(value) { + Vue.nextTick(() => { + wrapper.setProps({ value }); + }); + } + } + }); + + await later(); + expect(wrapper).toMatchSnapshot(); +}); + +test('animate', async () => { + const onInput = jest.fn(); + mount(Circle, { + propsData: { + rate: 50, + speed: 100 + }, + listeners: { + input: onInput + } + }); + + await later(50); + expect(onInput).toHaveBeenCalled(); + expect(onInput.mock.calls[0][0]).not.toEqual(0); +});