【SVG篇一】SVG基本形状转换成path

5,984次阅读
没有评论

共计 7981 个字符,预计需要花费 20 分钟才能阅读完成。

前言

之前文章有介绍过 svgo 的不错的 svg 压缩工具,其实,svg 基本形状转换成 path,也是一种压缩方式,不但可以压缩,而且可以做一些路径动画,路径动画下一篇详细介绍,本文主要介绍一下 svg 基本形状如何转换成 path。

SVG path 路径

svg 形状很简单,常见的形状一般如下:


   
   
  
 
 
  
 
 
  
 
 
  
 
 
  
 
 
  
 

对上面基本形状转换了,那么基本就完成了。

我们介绍一下 SVG path 的相关命令

如下图:

enter image description here

通常大部分形状,都可以通过指令 M(m)、L(l)、H(h)、V(v)、A(a) 来实现,注意特别要区分大小写,相对与绝对坐标情况,转换时推荐使用相对路径可减少代码量,例如:

// 以下两个等价
d='M 10 10 20 20'     // (10, 10) (20 20) 都是绝对坐标
d='M 10 10 L 20 20'

// 以下两个等价
d='m 10 10 20 20'     // (10, 10) 绝对坐标, (20 20) 相对坐标
d='M 10 10 l 20 20'

react to path

如下图所示,一个 rect 是由 4 个弧和 4 个线段构成;如果 rect 没有设置 rx 和 ry 则 rect 只是由 4 个线段构成。rect 转换为 path 只需要将 A ~ H 之间的弧和线段依次实现即可。

【SVG 篇一】SVG 基本形状转换成 path

转换方法如下:

  function rect2path(x, y, width, height, rx, ry) {
          /*
          * rx 和 ry 的规则是:* 1. 如果其中一个设置为 0 则圆角不生效
          * 2. 如果有一个没有设置则取值为另一个
          */
          rx = rx || ry || 0;
          ry = ry || rx || 0;
          // 非数值单位计算,如当宽度像 100% 则移除
          if (is NaN(x - y + width - height + rx - ry)) return;
          rx = rx > width / 2 ? width / 2 : rx;
          ry = ry > height / 2 ? height / 2 : ry;
          // 如果其中一个设置为 0 则圆角不生效
          if(0 == rx || 0 == ry){
          // var path =
          // 'M' + x + ' ' + y +
          // 'H' + (x + width) + 不推荐用绝对路径,相对路径节省代码量
          // 'V' + (y + height) +
          // 'H' + x +
          // 'z';
          var path =
          'M' + x + '' + y +'h'+ width +'v'+ height +'h'+ -width +'z';
} else{
 var path =
    'M' + x + '' + (y+ry) +'a'+ rx +' '+ ry +' 0 0 1 '+ rx +' '+ (-ry) +'h'+ (width - rx - rx) +'a'+ rx +' '+ ry +' 0 0 1 '+ rx +' '+ ry +'v'+ (height - ry -ry) +'a'+ rx +' '+ ry +' 0 0 1 '+ (-rx) +' '+ ry +'h'+ (rx + rx -width) +'a'+ rx +' '+ ry +' 0 0 1 '+ (-rx) +' '+ (-ry) +'z';
}
   return path;
}

circle/ellipse to path

圆可视为是一种特殊的椭圆,即 rx 与 ry 相等的椭圆,所以可以放在一起讨论。椭圆可以看成 A 点到 C 做 180 度顺时针画弧、C 点到 A 做 180 度顺时针画弧即可。

如下:

【SVG 篇一】SVG 基本形状转换成 path

转换代码如下:

 function ellipse2path(cx, cy, rx, ry) {
    // 非数值单位计算,如当宽度像 100% 则移除
    if (is NaN(cx - cy + rx - ry)) return;
    var path =
    'M' + (cx -rx) + '' + cy +'a'+ rx +' '+ ry +' 0 1 0 '+ 2* rx +' 0'+'a'+ rx +' '+ ry +' 0 1 0 '+ (-2* rx) +' 0'+'z';
    return path;
}

line to path

function line2path(x1, y1, x2, y2) {
    // 非数值单位计算,如当宽度像 100% 则移除
    if (isNaN(x1 - y1 + x2 - y2)) return;
    x1 = x1 || 0;
    y1 = y1 || 0;
    x2 = x2 || 0;
    y2 = y2 || 0;
    var path = 'M' + x1 + ''+ y1 +'L'+ x2 +' ' + y2;
    return path;
}

polyline/polygon to path

polyline 折线、polygon 多边形的转换为 path 比较类似,差别就是 polygon 多边形会闭合。

 // polygon 折线转换
//points = [x1, y1, x2, y2, x3, y3 ...];
function polyline2path (points) {var path = 'M' + points.slice( 0, 2).join('') +'L'+ points.slice(2).join(' ');
    return path;
}
// polygon 多边形转换
//points = [x1, y1, x2, y2, x3, y3 ...];
function polygon2path (points) {var path = 'M' + points.slice( 0, 2).join('') +'L'+ points.slice(2).join(' ') +'z';
  return path;
}

SVG 基本形状转换成 path 完整转换代码

下面是封装的一个完整转换代码:

(function () {// 是否支持 url() 函数和基本图形函数的判断
    var isSupportUrl = CSS.supports('offset-path', 'url(#xxx)');
    var isSupportBasicShape = CSS.supports('offset-path', 'circle()');

    window.convertPathData = function (node) {
      // 匹配路径中数值的正则
      var regNumber = /[-+]?(?:d*.d+|d+.?)(?:[eE][-+]?d+)?/g;

      if (!node.tagName) return
      var tagName = String(node.tagName).toLowerCase()

      switch (tagName) {
        case 'path':
            var path = node.getAttribute('d');
            break;
        case 'rect':
          var x = Number(node.getAttribute('x'))
          var y = Number(node.getAttribute('y'))
          var width = Number(node.getAttribute('width'))
          var height = Number(node.getAttribute('height'))
          /*
           * rx 和 ry 的规则是:* 1. 如果其中一个设置为 0 则圆角不生效
           * 2. 如果有一个没有设置则取值为另一个
           * 3.rx 的最大值为 width 的一半, ry 的最大值为 height 的一半
           */
          var rx = Number(node.getAttribute('rx')) || Number(node.getAttribute('ry')) || 0
          var ry = Number(node.getAttribute('ry')) || Number(node.getAttribute('rx')) || 0

          // 非数值单位计算,如当宽度像 100% 则移除
          // if (isNaN(x - y + width - height + rx - ry)) return;

          rx = rx > width / 2 ? width / 2 : rx
          ry = ry > height / 2 ? height / 2 : ry

          // 如果其中一个设置为 0 则圆角不生效
          if (rx == 0 || ry == 0) {
            // var path =
            //     'M' + x + ' ' + y +
            //     'H' + (x + width) +
            //     'V' + (y + height) +
            //     'H' + x +
            //     'z';
            var path =
              'M' + x + '' + y +'h'+ width +'v'+ height +'h'+ -width +'z'
          } else {
            var path =
              'M' +
              x +
              ' ' +
              (y + ry) +
              'a' +
              rx +
              ' ' +
              ry +
              '0 0 1' +
              rx +
              ' ' +
              -ry +
              'h' +
              (width - rx - rx) +
              'a' +
              rx +
              ' ' +
              ry +
              '0 0 1' +
              rx +
              ' ' +
              ry +
              'v' +
              (height - ry - ry) +
              'a' +
              rx +
              ' ' +
              ry +
              '0 0 1' +
              -rx +
              ' ' +
              ry +
              'h' +
              (rx + rx - width) +
              'a' +
              rx +
              ' ' +
              ry +
              '0 0 1' +
              -rx +
              ' ' +
              -ry +
              'z'
          }

          break

        case 'circle':
          var cx = node.getAttribute('cx')
          var cy = node.getAttribute('cy')
          var r = node.getAttribute('r')
          var path =
            'M' +
            (cx - r) +
            ' ' +
            cy +
            'a' +
            r +
            ' ' +
            r +
            '0 1 0' +
            2 * r +
            '0' +
            'a' +
            r +
            ' ' +
            r +
            '0 1 0' +
            -2 * r +
            '0' +
            'z'

          break

        case 'ellipse':
          var cx = node.getAttribute('cx') * 1
          var cy = node.getAttribute('cy') * 1
          var rx = node.getAttribute('rx') * 1
          var ry = node.getAttribute('ry') * 1

          if (isNaN(cx - cy + rx - ry)) return
          var path =
            'M' +
            (cx - rx) +
            ' ' +
            cy +
            'a' +
            rx +
            ' ' +
            ry +
            '0 1 0' +
            2 * rx +
            '0' +
            'a' +
            rx +
            ' ' +
            ry +
            '0 1 0' +
            -2 * rx +
            '0' +
            'z'

          break

        case 'line':
          var x1 = node.getAttribute('x1')
          var y1 = node.getAttribute('y1')
          var x2 = node.getAttribute('x2')
          var y2 = node.getAttribute('y2')
          if (isNaN(x1 - y1 + (x2 - y2))) {return}

          var path = 'M' + x1 + '' + y1 +'L'+ x2 +' ' + y2

          break

        case 'polygon':
        case 'polyline': // ploygon 与 polyline 是一样的,polygon 多边形,polyline 折线
          var points = (node.getAttribute('points').match(regNumber) || []).map(Number)
          if (points.length {1,4} round );
                    // 
                    // 语法的圆角部分可能会对应不上
                    var eleRect = document.createElement('rect');
                    var sizeRect = paramOffsetPath.split('round')[0].trim();
                    var sizeRadius = (paramOffsetPath.split('round')[1] || 0).trim();

                    // 一些坐标参数值
                    var x = 0;
                    var y = 0;
                    var rx = 0;
                    var ry = 0;

                    // 1- 4 个值处理
                    var arrOffset = sizeRect.split(/s+/);

                    if (arrOffset.length == 0) {arrOffset = [0, 0, 0, 0];
                    } else if (arrOffset.length == 1) {arrOffset = [arrOffset[0], arrOffset[0], arrOffset[0], arrOffset[0]];
                    } else if (arrOffset.length == 2) {arrOffset = [arrOffset[0], arrOffset[1], arrOffset[0], arrOffset[1]];
                    } else if (arrOffset.length == 3) {arrOffset = [arrOffset[0], arrOffset[1], arrOffset[2], arrOffset[1]];
                    }
                    // 百分比值转换成固定的长度值
                    arrOffset = arrOffset.map(function (offset, index) {if (/%$/.test(offset)) {return [height, width, height, width][index] * parseFloat(offset) / 100;
                        }
                        return parseFloat(offset) || 0;
                    });

                    // 圆角的处理
                    var arrRadius = sizeRadius.split('/');

                    rx = (function () {var radius = arrRadius[0];
                        if (/%$/.test(radius)) {return width * parseFloat(radius) / 100;
                        }
                        return parseFloat(radius) || 0;
                    })();
                    ry = (function () {var radius = arrRadius[1];

                        if (!radius) {return cx;}
                        if (/%$/.test(radius)) {return height * parseFloat(radius) / 100;
                        }
                        return parseFloat(radius) || 0;
                    })();

                    // 设置尺寸
                    x = arrOffset[3];
                    y = arrOffset[0];

                    var w = width - arrOffset[3] - arrOffset[1];
                    var h = height - arrOffset[0] - arrOffset[2];

                    eleRect.setAttribute('x', x);
                    eleRect.setAttribute('y', y);
                    eleRect.setAttribute('width', w);
                    eleRect.setAttribute('height', h);
                    eleRect.setAttribute('rx', rx);
                    eleRect.setAttribute('ry', ry);

                    path = convertPathData(eleRect);
                } else if (/^circle(/i.test(cssVarValueOffsetPath)) {var eleCircle = document.createElement('circle');
                    var cx, cy, r;
                    // 圆语法变成路径语法
                    r = paramOffsetPath.split('at')[0].trim() || '50%';

                    if (/%$/.test(r)) {r = Math.min(width, height) * parseFloat(r) / 100;
                    } else {r = parseFloat(r);
                    }

                    var cxCy = paramOffsetPath.split('at')[1] || '50% 50%';
                    cxCy = cxCy.trim().split(/s+/);
                    if (cxCy.length == 1) {cxCy = cxCy.push(cxCy[0]);
                    }
                    cxCy = cxCy.map(function (xy, index) {if (/%$/.test(xy)) {return [width, height][index] * parseFloat(xy) / 100;
                        }
                        return parseFloat(xy) || 0;                    
                    });

                    cx = cxCy[0];
                    cy = cxCy[1];

                    eleCircle.setAttribute('cx', cx);
                    eleCircle.setAttribute('cy', cy);
                    eleCircle.setAttribute('r', r);

                    path = convertPathData(eleCircle);
                } else if (/^ellipse(/i.test(cssVarValueOffsetPath)) {// ellipse( [ {2} ]? [at  ]? )
                    // 
                    var eleEllipse = document.createElement('ellipse');
                    var cx, cy, rx, ry;
                    // 圆语法变成路径语法
                    rxRy = paramOffsetPath.split('at')[0].trim() || '50% 50%';

                    rxRy = rxRy.split(/s+/);
                    if (rxRy.length == 1) {rxRy = rxRy.push(rxRy[0]);
                    }
                    rxRy = rxRy.map(function (xy, index) {if (/%$/.test(xy)) {return [width, height][index] * parseFloat(xy) / 100;
                        }
                        return parseFloat(xy) || 0;                    
                    });

                    var cxCy = paramOffsetPath.split('at')[1] || '50% 50%';
                    cxCy = cxCy.trim().split(/s+/);
                    if (cxCy.length == 1) {cxCy = cxCy.push(cxCy[0]);
                    }
                    cxCy = cxCy.map(function (xy, index) {if (/%$/.test(xy)) {return [width, height][index] * parseFloat(xy) / 100;
                        }
                        return parseFloat(xy) || 0;                    
                    });

                    rx = rxRy[0];
                    ry = rxRy[1];

                    cx = cxCy[0];
                    cy = cxCy[1];



                    eleEllipse.setAttribute('cx', cx);
                    eleEllipse.setAttribute('cy', cy);
                    eleEllipse.setAttribute('rx', rx);
                    eleEllipse.setAttribute('ry', ry);

                    path = convertPathData(eleEllipse);
                } else if (/^polygon(/i.test(cssVarValueOffsetPath)) {
                    // 多边形的处理
                    var arrPoints = paramOffsetPath.split(/,s*/);
                    // 变成百分比值变成固定值
                    arrPoints = arrPoints.map(function (strXy) {return strXy.split(/s+/).map(function (xy, index) {if (/%$/.test(xy)) {return [width, height][index] * parseFloat(xy) / 100;
                            }
                            return parseFloat(xy) || 0;
                        }).join(' ');
                    });

                    path = 'M' + arrPoints.join('L') + 'Z';
                }
            }

            if (path) {node.style.setProperty('--offset-path', 'path("'+ path +'")');
            }
        };

        // DOM Insert 自动初始化
        if (window.MutationObserver) {var observerSelect = new MutationObserver(function (mutationsList) {mutationsList.forEach(function (mutation) {
                    var nodeAdded = mutation.addedNodes;

                    if (nodeAdded.length) {nodeAdded.forEach(function (eleAdd) {funSetOffsetPath(eleAdd);
                        });
                    }
                });
            });

            observerSelect.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        // 如果没有开启自动初始化,则返回
        document.querySelectorAll(selector).forEach(function (ele) {funSetOffsetPath(ele);
        });
    };

    if (document.readyState != 'loading') {funAutoInitAndWatching();
    } else {window.addEventListener('DOMContentLoaded', funAutoInitAndWatching);
    }
})();

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计7981字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)