经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » SVG » 查看文章
沿SVG路径的颜色渐变
来源:cnblogs  作者:爱喝可乐的咖啡  时间:2024/2/18 9:03:10  对本文有异议

原生的渐变方法

在SVG中提供的原生渐变方法有两种,分别为线性渐变linearGradient和径向渐变radialGradient。我们以一个稍微复杂的路径来作为模板,为其添加两种渐变效果:

  1. <svg width="800" height="300">
  2. <defs>
  3. <linearGradient id="linear-grad">
  4. <stop offset="0" stop-color="#f7ff00" />
  5. <stop offset="1" stop-color="#db36a4" />
  6. </linearGradient>
  7. <radialGradient id="radial-grad">
  8. <stop offset="0" stop-color="#f7ff00" />
  9. <stop offset="1" stop-color="#db36a4" />
  10. </radialGradient>
  11. <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200"/>
  12. </defs>
  13. <use xlink:href="#grad-path" stroke="black" fill="none" stroke-width="3" />
  14. <use xlink:href="#grad-path" stroke="url(#linear-grad)" fill="none" stroke-width="9" x="250" />
  15. <use xlink:href="#grad-path" stroke="url(#radial-grad)" fill="none" stroke-width="9" x="500" />
  16. </svg>

展示效果如下:

虽然这两种渐变类型还有许多其他的属性可以设置,但它们的渐变颜色始终是只能沿着某一方向直线分布的,无法满足我们希望颜色可以沿着任意路径作渐变的需求。

对于某些特殊的路径如<circle> 可以通过一些比较hack的方式来仅依靠CSS就能实现沿路径颜色渐变:例如这个示例。但我们需要的是足够通用的办法,可以做到任意路径上的颜色渐变;这就需要借助JavaScript来实现。

模拟渐变

既然SVG没有原生提供我们想要的渐变效果,就需要想想其他办法来“曲线救国”。

我们首先考虑下在Canvas画布中要实现沿路径渐变颜色是如何实现的:Canvas的2D上下文同样是只提供了线性渐变createLinearGradient()和径向渐变createRadialGradient()两种方法。但不同之处在于,Canvas画布是可以逐像素绘制颜色的。在绘制路径时根据渐变颜色做插值计算,得出每个像素应用的颜色值即可。

将这种思考迁移到SVG中来。尽管SVG是矢量图绘制,不支持像素级操作的;但通过方法getTotalLength()getPointAtLength()可以获得路径的总长度及组成路径的这些离散点在SVG视图内的坐标。再利用<circle>图形来模拟“像素点”,依次摆放到前述的点坐标上,再设置对应的渐变颜色:

这些“像素点”越密集呈现的效果就越好。出于对页面性能的考虑,事先选定一个合适的密度很重要。

  1. <svg width="800" height="300">
  2. <defs>
  3. <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200" />
  4. </defs>
  5. <g id="dots-container">
  6. <!-- 放置circle的容器 -->
  7. </g>
  8. </svg>
  9. <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
  10. <script src="./createDots.js"></script>

这里引入d3.js库只是为了方便做颜色的插值计算。

  1. const strokeWidth = 9
  2. const startColor = '#f7ff00', endColor = '#db36a4'
  3. const gradientPath = document.querySelector('#grad-path')
  4. const dotsContainer = document.querySelector('#dots-container')
  5. // 选择合适的点间距来控制密度
  6. //const dotsDensity = strokeWidth * 1.0
  7. const dotsDensity = strokeWidth * 0.2
  8. const numberOfDots = Math.ceil(gradientPath.getTotalLength() / dotsDensity)
  9. createDots()
  10. function createDots() {
  11. for(let i = 0; i < numberOfDots; ++i) {
  12. const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
  13. dotsContainer.appendChild(circle)
  14. const steps = i / numberOfDots
  15. // 计算坐标点
  16. const pos = gradientPath.getPointAtLength(steps * gradientPath.getTotalLength())
  17. circle.setAttribute('cx', pos.x)
  18. circle.setAttribute('cy', pos.y)
  19. circle.setAttribute('r', strokeWidth / 2)
  20. // 计算颜色插值
  21. const interpolator = d3.interpolate(startColor, endColor)
  22. const curColor = interpolator(steps)
  23. circle.setAttribute('fill', curColor)
  24. }
  25. }

动画

接下来我们再为其加入动画,使渐变颜色沿着路径移动来产生动效。

  1. <svg width="800" height="300">
  2. <defs>
  3. <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200" />
  4. </defs>
  5. <use xlink:href="#grad-path" stroke="#868e96" fill="none" stroke-width="1" />
  6. <g id="dots-container">
  7. <!-- 放置circle的容器 -->
  8. </g>
  9. </svg>
  10. <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
  11. <script src="./test.js"></script>

除了渐变色外,定义动效的宽度、高度及时长:

  1. const startColor = '#f7ff00', endColor = '#db36a4'
  2. const gradientPath = document.querySelector('#grad-path')
  3. const dotsContainer = document.querySelector('#dots-container')
  4. // 动效的宽高
  5. const strokeWidth = 9
  6. const effectLength = 100
  7. // 选择合适的点间距来控制密度
  8. const dotsDensity = strokeWidth * 0.4
  9. const numberOfDots = Math.ceil(effectLength / dotsDensity)
  10. // 动画时长
  11. const duration = 1500

根据配置创建所有的像素点,初始化颜色与半径等属性后将其引用存储起来:

  1. // 初始化所有点
  2. const dots = []
  3. createDots()
  4. function createDots() {
  5. for(let i = 0; i < numberOfDots; ++i) {
  6. const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
  7. dotsContainer.appendChild(circle)
  8. const color = d3.interpolate(startColor, endColor)(i / numberOfDots)
  9. circle.setAttribute('fill', color)
  10. circle.setAttribute('display', 'none')
  11. circle.setAttribute('r', strokeWidth / 2)
  12. dots.push(circle)
  13. }
  14. }

执行动画。由window.requestAnimationFrame决定动画的帧数。传入每帧的回调函数,从动效的尾部开始处理,由dotsDensity做像素点的间隔向前依次更新位置,对于不在路径范围内的点做隐藏处理:

  1. window.requestAnimationFrame(animationTask)
  2. let startTime = -1
  3. function animationTask(timestamp) {
  4. if(startTime === -1) {
  5. startTime = timestamp
  6. }
  7. const process = (timestamp - startTime) / duration
  8. // 动效的终点坐标
  9. const tailIdx = process * gradientPath.getTotalLength()
  10. for(let i = numberOfDots - 1; i >= 0; --i) {
  11. const posIdx = tailIdx - (numberOfDots - i) * dotsDensity
  12. const hide = posIdx < 0 || posIdx > gradientPath.getTotalLength()
  13. const pos = gradientPath.getPointAtLength(posIdx)
  14. updateDot(i, pos, hide)
  15. }
  16. window.requestAnimationFrame(animationTask)
  17. }
  18. function updateDot(idx, pos, isHide) {
  19. const circle = dots[idx]
  20. if(isHide) {
  21. circle.setAttribute('display', 'none')
  22. } else {
  23. circle.setAttribute('display', 'inline')
  24. circle.setAttribute('cx', pos.x)
  25. circle.setAttribute('cy', pos.y)
  26. }
  27. }

最终效果如下~??

原文链接:https://www.cnblogs.com/geek1116/p/18017664

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号