原生的渐变方法

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

<svg width="800" height="300">
<defs>
<linearGradient id="linear-grad">
<stop offset="0" stop-color="#f7ff00" />
<stop offset="1" stop-color="#db36a4" />
</linearGradient>
<radialGradient id="radial-grad">
<stop offset="0" stop-color="#f7ff00" />
<stop offset="1" stop-color="#db36a4" />
</radialGradient> <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200"/>
</defs> <use xlink:href="#grad-path" stroke="black" fill="none" stroke-width="3" />
<use xlink:href="#grad-path" stroke="url(#linear-grad)" fill="none" stroke-width="9" x="250" />
<use xlink:href="#grad-path" stroke="url(#radial-grad)" fill="none" stroke-width="9" x="500" />
</svg>

展示效果如下:

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

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

模拟渐变

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

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

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

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

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

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

const strokeWidth = 9
const startColor = '#f7ff00', endColor = '#db36a4'
const gradientPath = document.querySelector('#grad-path')
const dotsContainer = document.querySelector('#dots-container')
// 选择合适的点间距来控制密度
//const dotsDensity = strokeWidth * 1.0
const dotsDensity = strokeWidth * 0.2
const numberOfDots = Math.ceil(gradientPath.getTotalLength() / dotsDensity) createDots() function createDots() {
for(let i = 0; i < numberOfDots; ++i) {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
dotsContainer.appendChild(circle) const steps = i / numberOfDots
// 计算坐标点
const pos = gradientPath.getPointAtLength(steps * gradientPath.getTotalLength())
circle.setAttribute('cx', pos.x)
circle.setAttribute('cy', pos.y)
circle.setAttribute('r', strokeWidth / 2)
// 计算颜色插值
const interpolator = d3.interpolate(startColor, endColor)
const curColor = interpolator(steps)
circle.setAttribute('fill', curColor)
}
}

动画

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

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

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

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

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

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

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

window.requestAnimationFrame(animationTask)
let startTime = -1
function animationTask(timestamp) {
if(startTime === -1) {
startTime = timestamp
} const process = (timestamp - startTime) / duration
// 动效的终点坐标
const tailIdx = process * gradientPath.getTotalLength()
for(let i = numberOfDots - 1; i >= 0; --i) {
const posIdx = tailIdx - (numberOfDots - i) * dotsDensity
const hide = posIdx < 0 || posIdx > gradientPath.getTotalLength() const pos = gradientPath.getPointAtLength(posIdx) updateDot(i, pos, hide)
} window.requestAnimationFrame(animationTask)
} function updateDot(idx, pos, isHide) {
const circle = dots[idx] if(isHide) {
circle.setAttribute('display', 'none') } else {
circle.setAttribute('display', 'inline')
circle.setAttribute('cx', pos.x)
circle.setAttribute('cy', pos.y)
}
}

最终效果如下~

沿SVG路径的颜色渐变的更多相关文章

  1. SVG.js 颜色渐变使用

    一.SVG.Gradient 1.线性渐变.径向渐变,设置渐变的起始点,设置径向渐变的外层半径 var draw = SVG('svg1').size(300, 300); //SVG.Gradien ...

  2. 深度掌握SVG路径path的贝塞尔曲线指令

    一.数字.公式.函数.变量,哦,NO! 又又一次说起贝塞尔曲线(英语:Bézier curve,维基百科详尽中文释义戳这里),我最近在尝试实现复杂的矢量图形动画,发现对贝塞尔曲线的理解馒头那么厚,是完 ...

  3. iOS 动画绘制线条颜色渐变的折线图

    效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...

  4. CAGradientLayer颜色渐变器

    使用CAGradientLayer可以实现颜色的渐变, 我们先看下头文件 @interface CAGradientLayer : CALayer @property(nullable, copy) ...

  5. SVG路径

    前面的话 本文将详细介绍SVG路径 path字符串 路径(path)是一个非常强大的绘图工具,可以用path元素绘制矩形(直角矩形或者圆角矩形).圆形.椭圆.折线形.多边形,以及一些其他的形状,例如贝 ...

  6. svg路径蒙版动画

    svg路径蒙版动画,是比较实用的一种动画效果,能够绘制如下图所示的动画. 接下来细说这样的动画是如何做成的: 1.准备工作 2.SVG路径动画 3.SVG路径蒙版动画 4.复杂图形的编辑技巧 1.准备 ...

  7. DrawSVG - SVG 路径动画 jQuery 插件

    jQuery DrawSVG 使用了 jQuery 内置的动画引擎实现 SVG 路径动画,用到了 stroke-dasharray 和 stroke-dashoffset 属性.DrawSVG 是完全 ...

  8. CSS3颜色渐变模式

       1.线性渐变:linear-gradient 语法:<linear-gradient> = linear-gradient([ [ <angle> | to <si ...

  9. 【iOS实现一个颜色渐变的弧形进度条】

    在Github上看到一些进度条的功能,都是通过Core Graph来实现.无所谓正确与否,但是开发效率明显就差很多了,而且运行效率还是值得考究的.其实使用苹果提供的Core Animation能够非常 ...

  10. IOS导航栏颜色渐变与常用属性

    (转:http://www.cnblogs.com/Lingchen-start/archive/2015/10/23/4904361.html) 今年很忙,忙的写日志的时间都很少.  少的可怜. 自 ...

随机推荐

  1. freeswitch修改mod_sofia模块并上报自定义头域

    概述 在之前的文章中,我们介绍了如何使用fs的event事件机制来获取呼叫的各种信息. 这些event事件一般都是底层模块定义好的,其中的各种信息已经很完备了,日常的开发需求都可以满足. 但是,总有一 ...

  2. Redis 常用五种数据类型编码

    转载请注明出处: 目录 Redis 的五种数据结构 Redis 数据结构的内部编码 1.String 1.1 常用命令 1.2 内部编码 1.3 典型使用场景 2. Hash 2.1 常用命令及时间复 ...

  3. JMS 服务器健康检查

    JMS所有服务器程序,包括Gateway.GatewayReferee.Proxy.TokenServer.以及编写的微服务器,都支持使用第三方工具进行健康检查. 使用telnet 进行健康检查 向任 ...

  4. Spring Boot对接Oracle数据库

    Spring Boot对接Oracle数据库 最近学习了Oracle数据库,那么如何使用Spring Boot和MyBatis Plus对接Oracle数据库呢? 这就有了这篇随记,具体流程如下 1. ...

  5. Python注释是什么东东

    注释 标注解释,目的是帮助读者理解的文本 也就是说,注释首先是文本,其二是说明,其三是思路,其四是例子注释有两种形式 1. # ... 单行注释 用于对某句语句或语句块进行解释 放在语句块的头行或一个 ...

  6. 幻兽帕鲁 Palworld 私有服务器一键部署教程

    <幻兽帕鲁>(日语:パルワールド,英语:Palworld) 是由日本开发商 Pocket Pair 推出的一款动作冒险生存游戏.游戏设定在一个由类似动物的生物 "帕鲁" ...

  7. [转帖]MySQL如何在InnoDB中重建索引并更新统计数据?

    https://geek-docs.com/mysql/mysql-ask-answer/356_mysql_how_can_i_rebuild_indexes_and_update_stats_in ...

  8. TiKV占用内存超过的解决过程

    TiKV占用内存超过的解决过程 背景 为了后去TiDB的极限数据. 晚上在每台服务器上面增加了多个TiKV的节点. 主要方式为: 每个NVME的硬盘增加两个TiKV的进程. 这样每个服务器两个磁盘, ...

  9. [转帖]tidb 搭建私有镜像库

    https://docs.pingcap.com/zh/tidb/stable/tiup-mirror 在构建私有云时,通常会使用隔离的网络环境,此时无法访问 TiUP 的官方镜像.因此,TiUP 提 ...

  10. 【转帖】nginx变量使用方法详解-6

    https://www.diewufeiyang.com/post/580.html Nginx 内建变量用在"子请求"的上下文中时,其行为也会变得有些微妙. 前面在 (三) 中我 ...