[new feature] Circle: support gradient color (#4157)

This commit is contained in:
neverland 2019-08-20 10:27:14 +08:00 committed by GitHub
parent a3712f7c5b
commit b7244f8fa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 346 additions and 64 deletions

View File

@ -37,20 +37,72 @@ export default {
};
```
### Custom style
### Custom Width
```html
<van-circle
v-model="currentRate"
color="#07c160"
fill="#fff"
size="120px"
layer-color="#ebedf0"
:text="text"
:rate="rate"
:speed="100"
:clockwise="false"
:stroke-width="60"
text="Custom Width"
/>
```
### Custom Color
```html
<van-circle
v-model="currentRate"
:rate="rate"
layer-color="#ebedf0"
text="Custom Color"
/>
```
### Gradient
```html
<van-circle
v-model="currentRate"
:rate="rate"
:color="gradientColor"
text="Gradient"
/>
```
``` javascript
export default {
data() {
return {
currentRate: 0,
gradientColor: {
'0%': '#ffd01e',
'100%': '#ee0a24'
}
};
}
};
```
### Counter Clockwise
```html
<van-circle
v-model="currentRate"
:rate="rate"
:clockwise="false"
text="Counter Clockwise"
/>
```
### Custom Size
```html
<van-circle
v-model="currentRate"
:rate="rate"
size="120px"
text="Custom Size"
/>
```
@ -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 speedrate/s| `number` | `0` |

View File

@ -39,20 +39,82 @@ export default {
};
```
### 样式定制
### 宽度定制
通过`stroke-width`属性来控制进度条宽度
```html
<van-circle
v-model="currentRate"
color="#07c160"
fill="#fff"
size="120px"
layer-color="#ebedf0"
:text="text"
:rate="rate"
:speed="100"
:clockwise="false"
:stroke-width="60"
text="宽度定制"
/>
```
### 颜色定制
通过`color`属性来控制进度条颜色,`layer-color`属性来控制轨道颜色
```html
<van-circle
v-model="currentRate"
:rate="rate"
layer-color="#ebedf0"
text="颜色定制"
/>
```
### 渐变色
`color`属性支持传入对象格式来定义渐变色
```html
<van-circle
v-model="currentRate"
:rate="rate"
:color="gradientColor"
text="渐变色"
/>
```
``` javascript
export default {
data() {
return {
currentRate: 0,
gradientColor: {
'0%': '#ffd01e',
'100%': '#ee0a24'
}
};
}
};
```
### 逆时针方向
`clockwise`设置为`false`,进度会从逆时针方向开始
```html
<van-circle
v-model="currentRate"
:rate="rate"
:clockwise="false"
text="逆时针方向"
/>
```
### 大小定制
通过`size`属性设置圆环直径
```html
<van-circle
v-model="currentRate"
:rate="rate"
size="120px"
text="大小定制"
/>
```
@ -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` | - |

View File

@ -5,39 +5,73 @@
v-model="currentRate1"
:rate="rate"
:speed="100"
size="120px"
:text="currentRate1.toFixed(0) + '%'"
/>
<div>
<van-button
:text="$t('add')"
type="primary"
size="small"
@click="add"
/>
<van-button
:text="$t('decrease')"
type="danger"
size="small"
@click="reduce"
/>
</div>
</demo-block>
<demo-block :title="$t('customStyle')">
<van-circle
v-model="currentRate2"
color="#07c160"
fill="#fff"
v-model="currentRate3"
:rate="rate"
size="120px"
layer-color="#ebedf0"
:speed="100"
:stroke-width="60"
:text="$t('customWidth')"
/>
<van-circle
v-model="currentRate3"
color="#f44"
:rate="rate"
layer-color="#ebedf0"
:speed="100"
:text="$t('customColor')"
/>
<van-circle
v-model="currentRate2"
:rate="rate"
:speed="100"
:color="gradientColor"
:text="$t('gradient')"
/>
<van-circle
v-model="currentRate4"
color="#07c160"
:rate="rate"
:speed="100"
:clockwise="false"
:text="currentRate2.toFixed(0) + '%'"
:text="$t('counterClockwise')"
style="margin-top: 15px;"
/>
<van-circle
v-model="currentRate4"
color="#7232dd"
:rate="rate"
:speed="100"
size="120px"
:clockwise="false"
:text="$t('customSize')"
style="margin-top: 15px;"
/>
</demo-block>
<div style="margin-top: 15px;">
<van-button
:text="$t('add')"
type="primary"
size="small"
@click="add"
/>
<van-button
:text="$t('decrease')"
type="danger"
size="small"
@click="reduce"
/>
</div>
</demo-section>
</template>
@ -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'
}
};
},

View File

@ -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) => (
<stop key={index} offset={key} stop-color={this.color[key]} />
));
return (
<defs>
<linearGradient id={this.uid} x1="100%" y1="0%" x2="0%" y2="0%">
{Stops}
</linearGradient>
</defs>
);
}
},
@ -116,8 +154,14 @@ export default createComponent({
return (
<div class={bem()} style={this.style}>
<svg viewBox="0 0 1060 1060">
<path class={bem('hover')} style={this.hoverStyle} d={PATH} />
<path class={bem('layer')} style={this.layerStyle} d={PATH} />
{this.LinearGradient}
<path class={bem('hover')} style={this.hoverStyle} d={this.path} />
<path
d={this.path}
class={bem('layer')}
style={this.layerStyle}
stroke={this.gradient ? `url(#${this.uid})` : this.color}
/>
</svg>
{this.slots() || (this.text && <div class={bem('text')}>{this.text}</div>)}
</div>

View File

@ -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 {

View File

@ -3,21 +3,51 @@
exports[`renders demo correctly 1`] = `
<div>
<div>
<div class="van-circle" style="width: 120px; height: 120px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__layer" style="stroke: #1989fa; stroke-dashoffset: 2198px; stroke-width: 41px;"></path>
<div class="van-circle" style="width: 100px; height: 100px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" stroke="#1989fa" class="van-circle__layer" style="stroke: #1989fa; stroke-width: 41px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">30%</div>
<div class="van-circle__text">70%</div>
</div>
<div><button class="van-button van-button--primary van-button--small"><span class="van-button__text">增加</span></button> <button class="van-button van-button--danger van-button--small"><span class="van-button__text">减少</span></button></div>
</div>
<div>
<div class="van-circle" style="width: 120px; height: 120px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__hover" style="fill: #fff; stroke: #ebedf0; stroke-width: 60px;"></path>
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__layer" style="stroke: #07c160; stroke-dashoffset: 4082px; stroke-width: 61px;"></path>
<div class="van-circle" style="width: 100px; height: 100px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" class="van-circle__hover" style="fill: none; stroke: #ebedf0; stroke-width: 60px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" stroke="#1989fa" class="van-circle__layer" style="stroke: #1989fa; stroke-width: 61px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">30%</div>
<div class="van-circle__text">宽度定制</div>
</div>
<div class="van-circle" style="width: 100px; height: 100px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" class="van-circle__hover" style="fill: none; stroke: #ebedf0; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" stroke="#f44" class="van-circle__layer" style="stroke: #f44; stroke-width: 41px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">颜色定制</div>
</div>
<div class="van-circle" style="width: 100px; height: 100px;"><svg viewBox="0 0 1060 1060">
<defs>
<linearGradient id="van-circle-gradient-3" x1="100%" y1="0%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#ffd01e"></stop>
<stop offset="100%" stop-color="#ee0a24"></stop>
</linearGradient>
</defs>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" stroke="url(#van-circle-gradient-3)" class="van-circle__layer" style="stroke: [object Object]; stroke-width: 41px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">渐变色</div>
</div>
<div class="van-circle" style="width: 100px; height: 100px; margin-top: 15px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 0 0, 1000 a 500, 500 0 1, 0 0, -1000" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 0 0, 1000 a 500, 500 0 1, 0 0, -1000" stroke="#07c160" class="van-circle__layer" style="stroke: #07c160; stroke-width: 41px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">逆时针</div>
</div>
<div class="van-circle" style="width: 120px; height: 120px; margin-top: 15px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 0 0, 1000 a 500, 500 0 1, 0 0, -1000" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 0 0, 1000 a 500, 500 0 1, 0 0, -1000" stroke="#7232dd" class="van-circle__layer" style="stroke: #7232dd; stroke-width: 41px; stroke-dasharray: 2198px 3140px;"></path>
</svg>
<div class="van-circle__text">大小定制</div>
</div>
</div>
<div style="margin-top: 15px;"><button class="van-button van-button--primary van-button--small"><span class="van-button__text">增加</span></button> <button class="van-button van-button--danger van-button--small"><span class="van-button__text">减少</span></button></div>
</div>
`;

View File

@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`speed is 0 1`] = `
<div class="van-circle" style="width: 100px; height: 100px;"><svg viewBox="0 0 1060 1060">
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
<path d="M 530 530 m 0, -500 a 500, 500 0 1, 1 0, 1000 a 500, 500 0 1, 1 0, -1000" stroke="#1989fa" class="van-circle__layer" style="stroke: #1989fa; stroke-width: 41px; stroke-dasharray: 1570px 3140px;"></path>
</svg></div>
`;

View File

@ -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);
});