简介:通过css圆锥渐变实现一个渐变圆弧效果。 实现效果如下:
使用css来实现的原因
首先说明下为什么要直接通过css来实现该效果。 实现环境是在微信小程序中,小程序是使用 Taro react 来实现。
在通过该方案实现之前也尝试使用canvas进行实现,当时只是在浏览器环境进行尝试写了个demo,具体demo实现效果如下:
具体代码如下:
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
ctx.beginPath()
ctx.moveTo(canvas.width / 2, canvas.height / 2)
ctx.arc(canvas.width / 2, canvas.height / 2, 100, Math.PI * 1.8, Math.PI * 2)
ctx.strokeStyle = '#000000'
ctx.closePath()
ctx.stroke()
ctx.beginPath()
ctx.lineWidth = 1
// 设置间距(参数为无限数组,虚线的样式会随数组循环)
ctx.setLineDash([5, 5]);
ctx.arc(canvas.width / 2, canvas.height / 2, 150, Math.PI * 1.6, Math.PI * 2)
ctx.strokeStyle = '#000000'
ctx.stroke()
ctx.closePath()
ctx.beginPath()
console.log('sin', Math.cos(Math.PI / 3) * 150);
ctx.moveTo(canvas.width / 2 + Math.cos(Math.PI * 0.6) * 150, canvas.height / 2 - 150)
// ctx.moveTo()
ctx.closePath()
ctx.beginPath()
const conicGradient = ctx.createConicGradient(0, canvas.width / 2, canvas.height / 2);
conicGradient.addColorStop(0, '#000000');
conicGradient.addColorStop(0.5, '#FFFFFF');
conicGradient.addColorStop(1, '#000000');
ctx.fillStyle = conicGradient
// ctx.moveTo(canvas.width / 2, canvas.height / 2)
ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, Math.PI * 2)
ctx.closePath()
ctx.fill()
ctx.beginPath()
ctx.lineWidth = 8
ctx.lineCap = 'round'
ctx.setLineDash([]);
ctx.arc(canvas.width / 2, canvas.height / 2, 180, 0, Math.PI * 1)
ctx.strokeStyle = conicGradient
ctx.stroke()
ctx.closePath()
// ctx.beginPath()
// ctx.arc(canvas.width - 8, canvas.height / 2, 4, 0, Math.PI * 2)
// ctx.closePath()
// ctx.fillStyle = '#000000'
// ctx.fill()
</script>
</body>
</html>
通过canvas画出该效果后想着在小程序中应该也差不多,以为这样就结束了。直到真正上手在小程序中实现渐变圆弧时,才发现情况好像不大一样,怎么在文档中找不到创建canvas锥形渐变的方法,有点不知所措了,然后尝试使用提供的线性渐变来实现一下,发现效果不太理想,颜色过渡会很生硬,不够自然,pass(个人感觉线性渐变也是可以实现圆润的颜色过渡,将圆弧分位很多份,给每一份以此加上颜色和透明度,感觉好像可行)。实现了一个半圆的渐变圆弧,具体效果如下:
之后想着🤔️,既然canvas的圆锥渐变不能使用,那么能不能使用background的圆锥渐变来实现呢?感觉上是可行的,只是圆弧是需要元素来遮挡实现,圆弧结束的位置过于生硬,不过这些问题感觉都是可以慢慢解决的。接下来直接开干!
css创建一个扇形
css
background: `conic-gradient(transparent ${startPercent}turn, ${color} ${startPercent}turn, transparent ${endPercent}turn)`,
具体效果:
然后设置一个较小的白色圆,进行遮挡就能够实现圆弧了,具体效果:
这个圆弧的开始处有些生硬,直接画一个小圆,通过三角函数计算对应位置(没一道数学题是白做的😭),定位到对应的位置上就可以了。
具体效果如下:
抽离出来具体实现代码如下:
js
const initVariable = {
width: 200,
height: 200,
borderWidth: 10,
}
const PieRoundLine = ({
startPercent = 0,
endPercent = 0.5,
color = 'yellowgreen',
}) => {
return (
<View
style={{
position: 'absolute',
width: initVariable.width,
height: initVariable.height,
background: `conic-gradient(transparent ${startPercent}turn, ${color} ${startPercent}turn, transparent ${endPercent}turn)`,
borderRadius: '50%',
padding: initVariable.borderWidth,
boxSizing: 'border-box',
}}
>
<View
style={{
position: 'absolute',
top:
initVariable.height / 2 -
Math.cos(startPercent * 2 * Math.PI) *
(initVariable.height / 2 - initVariable.borderWidth / 2),
left:
initVariable.width / 2 +
Math.sin(startPercent * 2 * Math.PI) *
(initVariable.width / 2 - initVariable.borderWidth / 2),
width: initVariable.borderWidth,
height: initVariable.borderWidth,
borderRadius: '50%',
background: `${color}`,
transform: `translate(-50%, -50%)`,
}}
></View>
<View
style={{
width: initVariable.width - 2 * initVariable.borderWidth,
height: initVariable.width - 2 * initVariable.borderWidth,
background: '#FFFFFF',
borderRadius: '50%',
boxSizing: 'border-box',
}}
></View>
</View>
)
}
饼图部分
以上部分完成后,整个效果已经算是差不多了,接下来只是实现饼图,通过对上面👆锥形渐变的应用,只需要注意一点点就能够很快的完成该功能了。
将需要展示的饼图位置设置为对应颜色,不需要的位置设置为透明的,多个饼图叠加在一起就能够实现里面的部分了。具体实现效果如下:
抽离出来实现代码如下:
js
const initPieRoundVariable = {
width: 130,
height: 130,
}
const PieRoundPart = ({
width = initPieRoundVariable.width,
height = initPieRoundVariable.height,
startPercent,
endPercent,
color,
}) => {
return (
<View
style={{
position: 'absolute',
top: '50%',
left: '50%',
width: width,
height: height,
background: `conic-gradient(transparent ${startPercent}turn, ${color} ${startPercent}turn, ${color} ${endPercent}turn, transparent ${endPercent}turn)`,
borderRadius: '50%',
transform: `translate(-50%, -50%)`,
zIndex: 1,
}}
></View>
)
}
结尾
最后将圆弧和饼图拼在一起就能实现开始的效果了
完整代码如下:
js
import Taro from '@tarojs/taro'
import { useEffect } from 'react'
import { View } from '@tarojs/components'
const initVariable = {
width: 200,
height: 200,
borderWidth: 10,
}
const PieRoundLine = ({
startPercent = 0,
endPercent = 0.5,
color = 'yellowgreen',
}) => {
return (
<View
style={{
position: 'absolute',
width: initVariable.width,
height: initVariable.height,
background: `conic-gradient(transparent ${startPercent}turn, ${color} ${startPercent}turn, transparent ${endPercent}turn)`,
borderRadius: '50%',
padding: initVariable.borderWidth,
boxSizing: 'border-box',
}}
>
<View
style={{
position: 'absolute',
top:
initVariable.height / 2 -
Math.cos(startPercent * 2 * Math.PI) *
(initVariable.height / 2 - initVariable.borderWidth / 2),
left:
initVariable.width / 2 +
Math.sin(startPercent * 2 * Math.PI) *
(initVariable.width / 2 - initVariable.borderWidth / 2),
width: initVariable.borderWidth,
height: initVariable.borderWidth,
borderRadius: '50%',
background: `${color}`,
transform: `translate(-50%, -50%)`,
}}
></View>
<View
style={{
width: initVariable.width - 2 * initVariable.borderWidth,
height: initVariable.width - 2 * initVariable.borderWidth,
background: '#FFFFFF',
borderRadius: '50%',
boxSizing: 'border-box',
}}
></View>
</View>
)
}
const initPieRoundVariable = {
width: 130,
height: 130,
}
const PieRoundPart = ({
width = initPieRoundVariable.width,
height = initPieRoundVariable.height,
startPercent,
endPercent,
color,
}) => {
return (
<View
style={{
position: 'absolute',
top: '50%',
left: '50%',
width: width,
height: height,
background: `conic-gradient(transparent ${startPercent}turn, ${color} ${startPercent}turn, ${color} ${endPercent}turn, transparent ${endPercent}turn)`,
borderRadius: '50%',
transform: `translate(-50%, -50%)`,
zIndex: 1,
}}
></View>
)
}
function PieChart() {
return (
<View
style={{
position: 'relative',
width: initVariable.width,
height: initVariable.height,
}}
>
<PieRoundLine startPercent={0} endPercent={0.6} color='red' />
<PieRoundLine startPercent={0.6} endPercent={0.7} color='black' />
<PieRoundLine startPercent={0.7} endPercent={1} color='pink' />
<PieRoundPart
startPercent={0}
endPercent={0.6}
color='red'
width={145}
height={145}
/>
<PieRoundPart startPercent={0.6} endPercent={0.7} color='black' />
<PieRoundPart startPercent={0.7} endPercent={1} color='pink' />
</View>
)
}
export default PieChart