当前位置: 首页 > news >正文

将css中的线性渐变,径向渐变,锥心渐变,转成canvas中的渐变

1.css中的linear-gradient转成canvas中

/*** 将CSS linear-gradient 转换为 Canvas 渐变* @param ctx Canvas 2D 上下文* @param cssGradient CSS linear-gradient 字符串,如 "linear-gradient(45deg, red 0%, blue 50%, green 100%)"* @param width Canvas 宽度* @param height Canvas 高度* @returns CanvasGradient 对象*/
export const cssLinearGradientToCanvas = (ctx: CanvasRenderingContext2D | null,cssGradient: string,width: number,height: number
): CanvasGradient | null => {if (!ctx) return null;// 解析 CSS linear-gradientconst gradientMatch = cssGradient.match(/linear-gradient\s*\(\s*([^)]+)\s*\)/);if (!gradientMatch) return null;const gradientContent = gradientMatch[1];const parts = gradientContent.split(',').map(part => part.trim());// 解析方向和角度let angle = 0;let colorStops: Array<{color: string, position: number}> = [];let startIndex = 0;// 检查第一个参数是否为角度或方向const firstPart = parts[0];if (firstPart.includes('deg')) {angle = parseFloat(firstPart.replace('deg', ''));startIndex = 1;} else if (firstPart.includes('to ')) {// 处理方向关键字const direction = firstPart.replace('to ', '');switch (direction) {case 'top': angle = 0; break;case 'right': angle = 90; break;case 'bottom': angle = 180; break;case 'left': angle = 270; break;case 'top right': angle = 45; break;case 'bottom right': angle = 135; break;case 'bottom left': angle = 225; break;case 'top left': angle = 315; break;default: angle = 180; // 默认从上到下}startIndex = 1;}// 解析颜色停止点for (let i = startIndex; i < parts.length; i++) {const colorStop = parts[i].trim();const colorMatch = colorStop.match(/^(.+?)\s+(\d+(?:\.\d+)?%?)$/);if (colorMatch) {const color = colorMatch[1].trim();const positionStr = colorMatch[2];let position: number;if (positionStr.includes('%')) {position = parseFloat(positionStr.replace('%', '')) / 100;} else {// 如果没有百分比,假设是像素值,转换为比例position = parseFloat(positionStr) / Math.max(width, height);}colorStops.push({ color, position });} else {// 如果没有指定位置,均匀分布const color = colorStop.trim();const position = (i - startIndex) / (parts.length - startIndex - 1);colorStops.push({ color, position });}}// 如果没有解析到颜色停止点,返回nullif (colorStops.length === 0) return null;// 计算渐变的起点和终点坐标const angleRad = (angle * Math.PI) / 180;// 计算渐变线的长度const gradientLength = Math.abs(width * Math.cos(angleRad)) + Math.abs(height * Math.sin(angleRad));// 计算起点和终点const centerX = width / 2;const centerY = height / 2;const x1 = centerX - (gradientLength / 2) * Math.cos(angleRad);const y1 = centerY - (gradientLength / 2) * Math.sin(angleRad);const x2 = centerX + (gradientLength / 2) * Math.cos(angleRad);const y2 = centerY + (gradientLength / 2) * Math.sin(angleRad);// 创建Canvas渐变const gradient = ctx.createLinearGradient(x1, y1, x2, y2);// 添加颜色停止点colorStops.forEach(stop => {gradient.addColorStop(Math.max(0, Math.min(1, stop.position)), stop.color);});return gradient;
}

解析linear-gradient的角度,其中比较难一点的地方就是gradientLength是如何计算的,我在网上找了一张图,可以大致解释一下,这个x1,y1,x2,y2是怎么计算的:
在这里插入图片描述
可以看到,其实我们计算的点,就是x0,y0,x1,y1,那我么首先需要知道(x0,y0)到(x1,y1)之间的距离。
在这里插入图片描述
两个点之间的距离,就是两条红色线条的长度,分别作图,可以得到两点之间的距离=wcos(a)+hsin(a)
得到了两点之间的距离,(x0,y0),就是中心点(w/2,h/2)减去距离的一半旋转角度,(x1,y1)就是中心点(w/2,h/2)加上距离的一半旋转角度

2.cssRadialGradientToCanvas转成canvas对应渐变

/*** 将CSS radial-gradient 转换为 Canvas 径向渐变* @param ctx Canvas 2D 上下文* @param cssGradient CSS radial-gradient 字符串,如 "radial-gradient(circle, red 0%, blue 100%)"* @param width Canvas 宽度* @param height Canvas 高度* @returns CanvasGradient 对象*/
export const cssRadialGradientToCanvas = (ctx: CanvasRenderingContext2D | null,cssGradient: string,width: number,height: number
): CanvasGradient | null => {if (!ctx) return null;// 解析 CSS radial-gradientconst gradientMatch = cssGradient.match(/radial-gradient\s*\(\s*([^)]+)\s*\)/);if (!gradientMatch) return null;const gradientContent = gradientMatch[1];const parts = gradientContent.split(',').map(part => part.trim());// 默认参数let centerX = width / 2;let centerY = height / 2;let radius = Math.min(width, height) / 2;let colorStops: Array<{color: string, position: number}> = [];let startIndex = 0;// 检查第一个参数是否为形状或位置const firstPart = parts[0];if (firstPart.includes('circle') || firstPart.includes('ellipse')) {startIndex = 1;// 解析位置 (at center, at top left 等)if (firstPart.includes('at ')) {const positionMatch = firstPart.match(/at\s+(.+)/);if (positionMatch) {const position = positionMatch[1].trim();const coords = parsePosition(position, width, height);centerX = coords.x;centerY = coords.y;}}} else if (firstPart.includes('at ')) {// 只有位置,没有形状const positionMatch = firstPart.match(/at\s+(.+)/);if (positionMatch) {const position = positionMatch[1].trim();const coords = parsePosition(position, width, height);centerX = coords.x;centerY = coords.y;}startIndex = 1;}// 解析颜色停止点for (let i = startIndex; i < parts.length; i++) {const colorStop = parts[i].trim();const colorMatch = colorStop.match(/^(.+?)\s+(\d+(?:\.\d+)?%?)$/);if (colorMatch) {const color = colorMatch[1].trim();const positionStr = colorMatch[2];let position: number;if (positionStr.includes('%')) {position = parseFloat(positionStr.replace('%', '')) / 100;} else {position = parseFloat(positionStr) / radius;}colorStops.push({ color, position });} else {// 如果没有指定位置,均匀分布const color = colorStop.trim();const position = (i - startIndex) / (parts.length - startIndex - 1);colorStops.push({ color, position });}}// 如果没有解析到颜色停止点,返回nullif (colorStops.length === 0) return null;// 创建径向渐变const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);// 添加颜色停止点colorStops.forEach(stop => {gradient.addColorStop(Math.max(0, Math.min(1, stop.position)), stop.color);});return gradient;
}/*** 解析CSS位置关键字*/
function parsePosition(position: string, width: number, height: number): {x: number, y: number} {const parts = position.split(/\s+/);let x = width / 2;let y = height / 2;parts.forEach(part => {switch (part) {case 'left': x = 0; break;case 'right': x = width; break;case 'top': y = 0; break;case 'bottom': y = height; break;case 'center': break; // 保持默认中心位置default:if (part.includes('%')) {const percent = parseFloat(part.replace('%', '')) / 100;if (parts.indexOf(part) === 0) {x = width * percent;} else {y = height * percent;}} else if (part.includes('px')) {const pixels = parseFloat(part.replace('px', ''));if (parts.indexOf(part) === 0) {x = pixels;} else {y = pixels;}}}});return { x, y };
}

3.锥形渐变转成canvas对应渐变

/*** 解析CSS位置关键字*/
function parsePosition(position: string, width: number, height: number): {x: number, y: number} {const parts = position.split(/\s+/);let x = width / 2;let y = height / 2;parts.forEach(part => {switch (part) {case 'left': x = 0; break;case 'right': x = width; break;case 'top': y = 0; break;case 'bottom': y = height; break;case 'center': break; // 保持默认中心位置default:if (part.includes('%')) {const percent = parseFloat(part.replace('%', '')) / 100;if (parts.indexOf(part) === 0) {x = width * percent;} else {y = height * percent;}} else if (part.includes('px')) {const pixels = parseFloat(part.replace('px', ''));if (parts.indexOf(part) === 0) {x = pixels;} else {y = pixels;}}}});return { x, y };
}/*** 将CSS conic-gradient 转换为 Canvas 锥形渐变* @param ctx Canvas 2D 上下文* @param cssGradient CSS conic-gradient 字符串,如 "conic-gradient(red, yellow, green, blue, red)"* @param width Canvas 宽度* @param height Canvas 高度* @returns CanvasGradient 对象*/
export const cssConicGradientToCanvas = (ctx: CanvasRenderingContext2D | null,cssGradient: string,width: number,height: number
): CanvasGradient | null => {if (!ctx) return null;// 检查浏览器是否支持 createConicGradientif (typeof ctx.createConicGradient !== 'function') {console.warn('当前浏览器不支持 createConicGradient');return null;}// 解析 CSS conic-gradientconst gradientMatch = cssGradient.match(/conic-gradient\s*\(\s*([^)]+)\s*\)/);if (!gradientMatch) return null;const gradientContent = gradientMatch[1];const parts = gradientContent.split(',').map(part => part.trim());// 默认参数let centerX = width / 2;let centerY = height / 2;let startAngle = 0; // 默认从顶部开始 (0度)let colorStops: Array<{color: string, position: number}> = [];let startIndex = 0;// 检查第一个参数是否为角度或位置const firstPart = parts[0];// 解析起始角度 (from 90deg)if (firstPart.includes('from ') && firstPart.includes('deg')) {const angleMatch = firstPart.match(/from\s+(\d+(?:\.\d+)?)deg/);if (angleMatch) {startAngle = (parseFloat(angleMatch[1]) * Math.PI) / 180;}startIndex = 1;}// 解析位置 (at center, at top left 等)if (firstPart.includes('at ')) {const positionMatch = firstPart.match(/at\s+(.+?)(?:\s+from|$)/);if (positionMatch) {const position = positionMatch[1].trim();const coords = parsePosition(position, width, height);centerX = coords.x;centerY = coords.y;}if (!firstPart.includes('from ')) {startIndex = 1;}}// 解析颜色停止点for (let i = startIndex; i < parts.length; i++) {const colorStop = parts[i].trim();const colorMatch = colorStop.match(/^(.+?)\s+(\d+(?:\.\d+)?(?:deg|%))$/);if (colorMatch) {const color = colorMatch[1].trim();const positionStr = colorMatch[2];let position: number;if (positionStr.includes('deg')) {// 角度转换为 0-1 的比例const degrees = parseFloat(positionStr.replace('deg', ''));position = (degrees % 360) / 360;} else if (positionStr.includes('%')) {position = parseFloat(positionStr.replace('%', '')) / 100;} else {position = parseFloat(positionStr) / 360; // 假设是角度}colorStops.push({ color, position });} else {// 如果没有指定位置,均匀分布const color = colorStop.trim();const position = (i - startIndex) / (parts.length - startIndex - 1);colorStops.push({ color, position });}}// 如果没有解析到颜色停止点,返回nullif (colorStops.length === 0) return null;// 创建锥形渐变const gradient = (ctx as any).createConicGradient(startAngle, centerX, centerY);// 添加颜色停止点colorStops.forEach(stop => {gradient.addColorStop(Math.max(0, Math.min(1, stop.position)), stop.color);});return gradient;
}

文章转载自:

http://5gUEJAUD.knqck.cn
http://MqdLS0OA.knqck.cn
http://JQ0oNw6f.knqck.cn
http://62TDFgrE.knqck.cn
http://S7sJyA2a.knqck.cn
http://aEEdWTEn.knqck.cn
http://JAsOWMk6.knqck.cn
http://dZ5dONe8.knqck.cn
http://B0R7flaN.knqck.cn
http://qQaJndR6.knqck.cn
http://BIFT8Hj2.knqck.cn
http://lQVYishh.knqck.cn
http://1bc11JUi.knqck.cn
http://vYpWL3eT.knqck.cn
http://HDOSiGEc.knqck.cn
http://J6A1dOu8.knqck.cn
http://5Txa737p.knqck.cn
http://eWctwuW3.knqck.cn
http://3yEOymaQ.knqck.cn
http://YQyCPJGL.knqck.cn
http://KNeXZgL2.knqck.cn
http://c2Ai7A8u.knqck.cn
http://g8Fj1ZD2.knqck.cn
http://xMOoiQlK.knqck.cn
http://cU4OBgEC.knqck.cn
http://3cFReE1Q.knqck.cn
http://i2kDvuyR.knqck.cn
http://WmnvLGtY.knqck.cn
http://dpcBUTRI.knqck.cn
http://p9uH33mb.knqck.cn
http://www.dtcms.com/a/363170.html

相关文章:

  • 【高并发内存池】三、线程缓存的设计
  • SpringBoot3中使用Caffeine缓存组件
  • Ruoyi-vue-plus-5.x第三篇Redis缓存与分布式技术:3.2 缓存注解与使用
  • 悬停头部的实现方式之一 css: position: sticky
  • SQL Server-查询事务日志
  • 血缘元数据采集开放标准:OpenLineage Guides 在 Spark 中使用 OpenLineage
  • B2B营销面临的一些主要问题
  • 3025. 人员站位的方案数 I
  • HDI线路板与普通板有何区别?厂家生产难度在哪?
  • 【leetcode】236. 二叉树的最近公共祖先
  • 《“人工智能+”行动意见》深度解析:从智能红利到产业落地,直播SDK的技术价值与应用路径
  • Kafka:Java开发的消息神器,你真的懂了吗?
  • 货运系统源码 货运物流小程序 货运平台搭建 货运软件开发
  • 深度学习——基于卷积神经网络实现食物图像分类【4】(使用最优模型)
  • Directus搜索功能:全文检索和高级过滤的技术实现
  • LeetCode22生成括号算法
  • 【开题答辩全过程】以 基于PHP的蔬菜食杂购物系统为例,包含答辩的问题和答案
  • 完全背包|dfs
  • qt安装FFmpeg后编译遇到error: collect2.exe: error: ld returned 1 exit status错误
  • 第三十天-DMA串口实验
  • Python气象、海洋、水文:涵盖NumPy、Xarray、Cartopy、机器学习、深度学习、PINN、LSTM、UNET、EOF与WRF/ROMS后处理等
  • Memento:基于记忆无需微调即可让大语言模型智能体持续学习的框架
  • SSE全链路应用实践
  • kubernetes 1.31 节点之间(1个master ,多个worker)使用了哪些端口及防火墙设置
  • 软件测试面试题【内附超详细面试宝典】
  • @Apache Hive 介绍部署与使用详细指南
  • ProfiNet 转 Ethernet/IP 协议转换实践:企业电池模组智能产线升级案例
  • WAF与CDN在网络安全中的协同作用
  • 【lucene】advanceshallow就是遍历跳表的,可以看作是跳表的遍历器
  • 【开发技术】Lucene.NET入门指南