填充SVG图像背景的路径

时间:2020-05-01 10:18:17

标签: html css svg

我有几个用于社交媒体链接的SVG图标。我希望他们有白色背景。在社区的帮助下,我发现可以通过在SVG代码<path fill="white" d="XXX">中添加XXX来完成,其中XXX等于d=""代码的第一部分,直到第一个m。例如,有以下SVG代码:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill='rgb(255, 64, 0)' viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/></svg>

要用白色填充内部,需要更改代码,如下所示:

<svg xmlns="http://www.w3.org/2000/svg" width='24' height='24' fill='rgb(255, 64, 0)' viewBox="0 0 24 24">
<path fill="white" d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12z"/>
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/>
</svg>

这就是添加的内容:

<path fill="white" d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12z"/>

此方法可以正常工作,直到出现这样的SVG:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">

<path d="M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm-3.233 10.62c-2.269 0-4.108-1.839-4.108-4.108 0-2.269 1.84-4.108 4.108-4.108s4.108 1.839 4.108 4.108c0 2.269-1.839 4.108-4.108 4.108zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm-1.604 3.31c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z"/></svg>

我尝试添加其他路径。这个仅填充内圆和圆点:

M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281z

这个填充了一点点,但是仍然不能完全填充:

M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z

然后有一个这样的SVG,它根本无法填充:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 12.713l-11.985-9.713h23.97l-11.985 9.713zm0 2.574l-12-9.725v15.438h24v-15.438l-12 9.725z"/></svg>

如果您能帮助我填补那些棘手的SVG,我将不胜感激。谢谢!

P.S。 用于编码SVG和实时预览的出色工具-here

1 个答案:

答案 0 :(得分:1)

概述

SVG具有丰富的DOM API,因此,如果存在无法填充的路径,那么如何构造一个可以做到的路径呢?

提出的方法包括以下步骤:

  1. 确定给定路径(“ O路径”)的凸包
  2. 从凸包的边界(“ CH-path”)构建路径
  3. 将CH路径插入O路径之前的SVG中。
  4. 用所需的颜色填充CH路径,保持O路径透明。

保证CH路径填充。由于SVG通过应用绘制程序的算法进行渲染,因此可以将CH路径用作O路径的背景,为O路径后面的抽象画布着色,并模拟大多数用户直观地认为路径的填充。 / p>

这个想法看似过大,但在野外遇到的大多数路径定义中都应该是可靠的(有关概念上的警告,请参见“限制”部分)。可以很容易地扩展它,以考虑用于绘制目标图标的一组路径元素。

限制

CH路径根据定义将覆盖O路径。相反,仅适用于凸形O路径。如果O形路径不是凸形的,则要根据情况逐个确定该解决方案是否仍然有用(例如,像吃豆人这样的图标比星形具有更好的几率)。

详细信息

  1. 计算凸包(CH) 点集的CH计算为完全覆盖这些点的最小凸点集。使用getPointAtLength元素的DOM API的getTotalLengthpath方法,无需分析路径规范即可获得合适的点集,因为这些方法允许以编程方式沿着路径移动,并且路径上的采样点(在下面的代码中,等距间隔为0.1个距离单位长度)。然后将所得的点集输入到CH算法中。

  2. 从CH路径构建路径 CH算法以正确的顺序返回CH点,以通过将相邻点与直线段连接来绘制边界。使用绝对坐标中的d(移至)和path(行至)路径命令,可以将其转换为M元素的L属性。

  3. 将CH路径插入到OG路径之前的SVG中。
    当将SVG元素的DOM API的方法insertAdjacentElement应用于O路径时,就可以解决问题。

  4. 用所需的颜色填充CH路径,保持O路径透明
    使用fill属性。

在线演示

该演示为了更好的可见性而对图标路径进行了转换,并绘制了CH边界。实际计算是在原始路径坐标上执行的。 CH用红色绘制。生产代码将利用某些库来计算CH。该演示显示了邮件图标,但包括电话和instagram图标的路径定义-超出了相应的use元素以使用它们。

<svg xmlns="http://www.w3.org/2000/svg" width="500" height="250" viewBox="0 0 2000 1000">
    <defs>
        <g
            id="icon_instagram"
            transform="translate(500,400) scale(20,20) translate(-10,-10)"
        >
            <path d="M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm-3.233 10.62c-2.269 0-4.108-1.839-4.108-4.108 0-2.269 1.84-4.108 4.108-4.108s4.108 1.839 4.108 4.108c0 2.269-1.839 4.108-4.108 4.108zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm-1.604 3.31c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z"/>
        </g>
        <g
            id="icon_mail"
            transform="translate(500,400) scale(40,40) translate(-12,-12.713)"
        >
            <path d="M12 12.713l-11.985-9.713h23.97l-11.985 9.713zm0 2.574l-12-9.725v15.438h24v-15.438l-12 9.725z"/>
        </g>
        <g
            id="icon_skype"
            transform="translate(500,400) scale(20,20) translate(-12, -12)"
        >
            <path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/>
        </g>
    </defs>
    <!-- only one 'use' element should be active at any one time -->  
    <use href="#icon_mail"/>
    <!--use href="#icon_instagram"/-->
    <!--use href="#icon_skype"/-->
    
    <text x="1100" y="150" width="200" height="100" style="font-size:300%;">Click on the filled area of the icon to the left.</text>
    
    <script type="text/javascript">
      <![CDATA[
        const SVG_XLINK = "http://www.w3.org/1999/xlink";
        const SVG_NS    = 'http://www.w3.org/2000/svg';
        
        function _cmp ( a, b ) {
            let n_cmp
              ;
              
            n_cmp = 
               (a.x > b.x)
                   ? 1
                   : ((a.x < b.x)
                        ? -1
                        : ((a.y > b.y)
                              ? 1
                              : ((a.y < b.y) ? -1 : 0)
                          )
                     )
                ;
            return n_cmp;
        } // _cmp
        
        // Get convex Hull of point set 
        function getConvexHull ( pa_points ) {
            console.log(`getConvexHull: started, point set size: ${pa_points.length};`); // `
            pa_points.sort ( _cmp );
            console.log(`getConvexHull: done: sort;`);
            
            let a_ch = []
              , m_base
              , n_base = 0
              , n_idxCurrentCandidate // index of current candidate for the next CH point
              , n_count = 0
              ;
            
            // !!! Reminder: svg y axis oriented downwards !
            // Sweep 1/2: x -> x+ (lower CH)
            let i = 0;
            while (pa_points[i].x === pa_points[0].x) {
//***console.log(`getConvexHull: pushing point #${i}.`);
                a_ch.push ( pa_points[i] ); // yep, points have been lexicographically ordered
                i++;
            }
            
            n_idxCurrentCandidate = i-1;
            while (n_idxCurrentCandidate + 1 < pa_points.length) { // guard against degenerate case
                n_base = n_idxCurrentCandidate;
                m_base = {
                   dx: (pa_points[n_base+1].x - pa_points[n_base].x)
                 , dy: (pa_points[n_base+1].y - pa_points[n_base].y)
                };
                n_idxCurrentCandidate = n_base+1;
                
                if (n_base+2 < pa_points.length) {
                    let k = n_base+2;
                    do {
                        let n_fac = (pa_points[k].x - pa_points[n_base].x) / m_base.dx
                          ;

                        if ((pa_points[k].y - pa_points[n_base].y) > n_fac * m_base.dy) {
                            // steeper slope detected.
                            n_idxCurrentCandidate = k;
                            m_base.dx = pa_points[k].x - pa_points[n_base].x;
                            m_base.dy = pa_points[k].y - pa_points[n_base].y;
                        }

                        k++;
                    } while ( k < pa_points.length );
                }
                
                // We will get the end point in the upper CH sweep.
                if (n_idxCurrentCandidate !== pa_points.length-1) {
//***console.log(`getConvexHull: pushing point #${n_idxCurrentCandidate}.`);
                    a_ch.push ( pa_points[n_idxCurrentCandidate] );
                }
                
                n_count++;
            } // lower CH

            console.log(`getConvexHull: lower CH completed after ${n_count} slope checks, ${a_ch.length} CH points found so far;`);

            // Sweep 2/2: x -> x- (upper CH)
            i = pa_points.length - 1;
            while (pa_points[i].x === pa_points[pa_points.length-1].x) {
//***console.log(`getConvexHull: pushing point #${i}.`);
                a_ch.push ( pa_points[i] ); // yep, points have been lexicographically ordered
                i--;
            }
            
            n_idxCurrentCandidate = i+1;
            while (n_idxCurrentCandidate - 1 >= 0) { // guard against degenerate case
                n_base = n_idxCurrentCandidate;
                m_base = {
                   dx: (pa_points[n_base-1].x - pa_points[n_base].x)
                 , dy: (pa_points[n_base-1].y - pa_points[n_base].y)
                };
                n_idxCurrentCandidate = n_base-1;
                
                if (n_base-2 >= 0) {
                    let k = n_base-2;
                    do {
                        let n_fac = (pa_points[k].x - pa_points[n_base].x) / m_base.dx
                          ;

                        if ((pa_points[k].y - pa_points[n_base].y) < n_fac * m_base.dy) {
                            // shallower slope detected.
                            n_idxCurrentCandidate = k;
                            m_base.dx = pa_points[k].x - pa_points[n_base].x;
                            m_base.dy = pa_points[k].y - pa_points[n_base].y;
                        }

                        k--;
                    } while ( k >= 0 );
                }
                
                // We got the start point already in the lower CH sweep.
                if (n_idxCurrentCandidate !== 0) {
//***console.log(`getConvexHull: pushing point #${n_idxCurrentCandidate}.`);
                    a_ch.push ( pa_points[n_idxCurrentCandidate] );
                }
                
                n_count++;
            } // upper CH

            console.log(`getConvexHull: upper CH completed. TL of ${n_count} slope checks, ${a_ch.length} CH points found.`);

            return a_ch;
        } // getConvexHull
        
        
        // Get convex hull of path
        function getPathHull ( e_path ) {
            console.log(`getPathHull: started;`);

            let a_CH
              , a_pointset = []
              , n_pathLengthCurrent = 0
              , n_pathLengthTotal   = e_path.getTotalLength()
              , n_steps = 10 * Math.floor(n_pathLengthTotal)
              ;
            console.log(`getPathHull: path length = ${n_pathLengthTotal};`);
            
            // Get point coords along the path.
            // Sliding [-1,+1]-window to eliminate interior points on straight line segments.
            let o_svgpoint_prevprev
              , o_svgpoint_prev
              , o_svgpoint
              ;
            
            a_pointset.push( e_path.getPointAtLength(0) );
            for (let i = 0; i < n_steps; i++) {
                o_svgpoint_prevprev = o_svgpoint_prev;
                o_svgpoint_prev     = o_svgpoint;
                o_svgpoint          = e_path.getPointAtLength(i * n_pathLengthTotal / n_steps); 
                if (i > 1) {
                    // Optimizing away points on straight line axis-parallel segments
                    if (!(
                            (
                                    (o_svgpoint_prevprev.x === o_svgpoint_prev.x)
                                &&  (o_svgpoint_prev.x     === o_svgpoint.x)
                            )
                        ||  (
                                    (o_svgpoint_prevprev.y === o_svgpoint_prev.y)
                                &&  (o_svgpoint_prev.y     === o_svgpoint.y)
                            )
                    )) {
                        a_pointset.push( o_svgpoint_prev );
                    }
                }
            }
            a_pointset.push( o_svgpoint ); // the last one - always included
            console.log(`getPathHull: pointset size = ${n_steps}, after straight line segment  optimization ${a_pointset.length};`);
            
            let n_checkAt = Math.floor(Math.random() * a_pointset.length)
              ;
            console.log(`getPathHull: @${Number(100 * n_checkAt / n_steps).toFixed(2)}%: (x,y) = (${Number(a_pointset[n_checkAt].x).toFixed(2)}, ${Number(a_pointset[n_checkAt].y).toFixed(2)});`);

            // Compute the Convex Hull for the point set
            a_CH = getConvexHull ( a_pointset );
            
            // Build a path from the CH. The CH point set is ordered counter-clockwise
            a_CH = a_CH.map ( (po_svgpoint) => {
                return { x: Number(po_svgpoint.x).toFixed(3), y: Number(po_svgpoint.y).toFixed(3) };
            });
            
            return a_CH;
        } // getPathHull

        //
        // paintPathHull
        //
        function paintPathHull ( eve, ps_id ) {
            console.log(`paintPathHull: started; eve.target.parentElement.id = '${eve.target.id}', ps_id = '${ps_id}'`);

            let a_CH
              , e_g     = eve.target.parentElement
              , e_hull
              , e_path  = document.querySelector(`#${e_g.getAttribute('id')} > path`)
              , e_svg   = document.querySelector('svg')
              , s_attr_d
              ;
              
            a_CH = getPathHull(e_path);
            console.log ( `a_CH - first 10 coords: ${JSON.stringify(a_CH.slice(0,9))};`); 
              
            s_attr_d = 
                `M${a_CH[0].x},${a_CH[0].y} `
              + a_CH.map( ( po_coords, pn_idx ) => {
                    return `L${po_coords.x},${po_coords.y}`;
                }).join(' ')
            ;
            e_hull = document.createElementNS(SVG_NS, 'path');
            e_hull.setAttribute('d', s_attr_d);
            e_hull.setAttribute('fill', 'green');
            e_hull.setAttribute('stroke', 'red');
            e_hull.setAttribute('stroke-width', '0.1');

            e_path.insertAdjacentElement('beforebegin', e_hull);
        } // paintPathHull
        
        document.querySelector('#icon_instagram').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_instagram'); } );
        document.querySelector('#icon_mail').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_mail'); } );
        document.querySelector('#icon_skype').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_skype'); } );
      ]]>
    </script>
</svg>