¿Puedes controlar cómo se dibuja el ancho de trazo de un SVG?

204

Actualmente construyendo una aplicación SVG basada en navegador. Dentro de esta aplicación, el usuario puede diseñar y posicionar varias formas, incluidos los rectángulos.

Cuando aplico stroke-widtha un rectelemento SVG de say 1px, el trazo se aplica al rectdesplazamiento y al recuadro del s de diferentes maneras por diferentes navegadores. Esto está resultando problemático, especialmente cuando trato de calcular el ancho exterior y la posición visual de un rectángulo y colocarlo junto a otros elementos.

Por ejemplo:

  • Firefox agrega 1px de inserción (abajo e izquierda) y 1px de compensación (arriba y derecha)
  • Chrome agrega 1px de inserción (superior e izquierda) y 1px de compensación (inferior y derecha)

Mi única solución hasta ahora sería dibujar los bordes reales yo mismo (probablemente con la pathherramienta) y colocar los bordes detrás del elemento trazado. Pero esta solución es una solución desagradable, y preferiría no seguir este camino si es posible.

Entonces mi pregunta es, ¿puedes controlar cómo stroke-widthse dibuja un SVG en los elementos?

Steve
fuente
hay trucos de filtro que puedes usar para lograr esto, pero no es una gran solución
Michael Mullany
2
Existe el paint-orderparámetro, donde puede especificar, que el relleno se debe representar en la parte superior del trazo, por lo que obtendrá la "alineación externa", consulte jsfiddle.net/hne0kyLg/1
Ivan Kuckir
Encontré una manera de hacer esto usando los atributos css 'outline-': codepen.io/badcat/pen/YVzmYY . No estoy seguro de cuál es el soporte para esto en todos los navegadores, pero podría ser útil.
bplmp

Respuestas:

375

No, no puede especificar si el trazo se dibuja dentro o fuera de un elemento. Hice una propuesta al grupo de trabajo SVG para esta funcionalidad en 2003, pero no recibió apoyo (ni discusión).

Ejemplo de ubicación de accidente cerebrovascular propuesto por SVG, de phrogz.net/SVG/stroke-location.svg

Como señalé en la propuesta,

  • puede lograr el mismo resultado visual que "dentro" doblando el ancho de su trazo y luego utilizando un trazado de recorte para recortar el objeto en sí mismo, y
  • puede lograr el mismo resultado visual que 'afuera' doblando el ancho del trazo y luego superponiendo una copia sin trazos del objeto encima de sí mismo.

Editar : esta respuesta puede ser incorrecta en el futuro. Debería ser posible para lograr estos resultados usando SVG efectos vectoriales , mediante la combinación veStrokePathcon veIntersect(por 'interior') o con veExclude(por 'exterior). Sin embargo, Vector Effects todavía es un módulo borrador en funcionamiento sin implementaciones que todavía pueda encontrar.

Edición 2 : la especificación del borrador SVG 2 incluye una stroke-alignmentpropiedad (con centro | dentro | fuera de los valores posibles). Esta propiedad puede llegar a ser UA eventualmente.

Edición 3 : Divertida y decepcionantemente, el grupo de trabajo SVG se ha eliminado stroke-alignmentde SVG 2. Puede ver algunas de las preocupaciones descritas después de la prosa aquí .

Phrogz
fuente
2
Pensé que ese podría ser el caso. Para resolver esto, he escrito una función de contenedor para getBBox () llamada getStrokedBBox (). Este contenedor devuelve el BBox de acuerdo con la forma en que el navegador aplica trazos al recuadro y al desplazamiento de una forma. No es perfecto (es necesario seguir comprobando las últimas versiones del navegador), pero por ahora proporciona un ancho exterior de forma precisa.
Steve
66
@Phrogz tal vez lo veremos antes de que hayan pasado 10 años. svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint anotación
francés el
1
¡Finalmente está aquí! Yo, por mi parte, realmente he estado instando a que llegue esta propiedad.
mes
He creado un script de SVG-contorno para trazar el contorno de cualquier SVGGeometryElement que se puede utilizar como una solución de ACV-alineación, para cualquier persona interesada puede encontrar una descripción aquí
maioman
2
¿Hay alguna página donde pueda votar a favor de la propuesta? Gracias. Parece ridículo que no sea compatible.
mahish
57

ACTUALIZACIÓN: El stroke-alignmentatributo fue el 1 de abril de 2015 movido a una especificación completamente nueva llamada SVG Strokes .

A partir del borrador del editor SVG 2.0 del 26 de febrero de 2015 (y posiblemente desde el 13 de febrero ),la stroke-alignmentpropiedad esta presentecon los valores inner, center (predeterminado) y outer.

Parece funcionar de la misma manera que la stroke-locationpropiedad propuesta por @Phrogz y la stroke-positionsugerencia posterior . Esta propiedad ha sido planificada desde al menos 2011, pero aparte de una anotación que decía

SVG 2 debe incluir una forma de especificar la posición del trazo

, nunca se ha detallado en la especificación ya que se aplazó , hasta ahora, al parecer.

Ningún navegador admite esta propiedad, o, que yo sepa, ninguna de las nuevas funciones de SVG 2, todavía, pero espero que lo hagan pronto a medida que la especificación madure. Esta ha sido una propiedad que personalmente he instado a tener, y estoy muy feliz de que finalmente esté allí en la especificación.

Parece haber algunos problemas en cuanto a cómo debe comportarse la propiedad en las rutas abiertas, así como en los bucles. Estos problemas, muy probablemente, prolongarán las implementaciones en todos los navegadores. Sin embargo, actualizaré esta respuesta con nueva información a medida que los navegadores comiencen a admitir esta propiedad.

mes
fuente
1
stroke-alignmentse especifica en SVG Strokes, un borrador de trabajo del W3C. Mientras tanto, el borrador del editor SVG 2 W3C dice que una propiedad de posicionamiento de trazo debe estar en la especificación SVG en svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint , pero esa especificación ya ha alcanzado el estado de recomendación de candidato W3C y no existe tal propiedad en la especificación aparte de un enlace a la stroke-positionpropuesta, haciendo que parezca que ese no será el caso.
Patrick Dark
47

Encontré una manera fácil, que tiene algunas restricciones, pero funcionó para mí:

  • definir la forma en defs
  • definir una ruta de recorte que haga referencia a la forma
  • úsalo y dobla el trazo con el exterior recortado

Aquí un ejemplo de trabajo:

<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
	<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
	<clipPath id="clip">
		<use xlink:href="#ld"/>
	</clipPath>
</defs>
<g>
	<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="#00D2B8" clip-path="url(#clip)"/>
</g>
</svg>

Jorg Janke
fuente
2
La mejor respuesta que encontré
Ed Kolosovsky
1
Solución inteligente Gracias
Vince Yuan
Esto debe ser aceptado como respuesta. Usar <defs> y <use> es la solución más elegante actualmente disponible.
Nyerguds
Buena solución ¿Cómo lo revertirías para hacer un golpe externo?
Lucent
¿Por qué el nivel superior <use>? ¿Por qué no simplemente poner la ruta y la ruta de recorte directamente? Esto funciona muy bien: <svg width="240" height="240" viewBox="0 0 1024 1024"> <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z" clip-path="url(#clip)" stroke="#0081C6" stroke-width="160" fill="#00d2b8"/> <clipPath id="clip"> <use xlink:href="#ld"/> </clipPath> </svg>. (Sí, esto es un SVG completamente razonable y correcto y se reproducirá correctamente en todas partes. No temas la recurrencia.)
Chris Morgan
30

Puede usar CSS para diseñar el orden de los trazos y rellenos. Es decir, golpee primero y luego llene segundo, y obtenga el efecto deseado.

MDN en paint-order: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order

Código CSS:

paint-order: stroke;
Xavier Ho
fuente
¡Esto es perfecto! Gracias por compartir
BrunoFenzl
1
¿La documentación vinculada no parece indicar si el trazo es interno, externo o centrado? ¿Podría aclarar cómo controlar dónde se dibuja el trazo? Gracias.
Crashalot
2
El trazo está centrado, como siempre lo estuvo, pero si tiene un relleno sólido, el efecto de ajustar el orden de pintura para pintar primero es que la mitad interna del trazo está pintada, lo que tiene el mismo efecto que dibujar un golpe medio grueso como el exterior.
Chris Morgan
7

Aquí hay una función que calculará cuántos píxeles necesita agregar, usando el trazo dado, en la parte superior, derecha, inferior e izquierda, todo en función del navegador:

var getStrokeOffsets = function(stroke){

        var strokeFloor =       Math.floor(stroke / 2),                                                                 // max offset
            strokeCeil =        Math.ceil(stroke / 2);                                                                  // min offset

        if($.browser.mozilla){                                                                                          // Mozilla offsets

            return {
                bottom:     strokeFloor,
                left:       strokeFloor,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }else if($.browser.webkit){                                                                                     // WebKit offsets

            return {
                bottom:     strokeCeil,
                left:       strokeFloor,
                top:        strokeFloor,
                right:      strokeCeil
            };

        }else{                                                                                                          // default offsets

            return {
                bottom:     strokeCeil,
                left:       strokeCeil,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }

    };
Steve
fuente
6

Como han señalado las personas anteriores, tendrá que volver a calcular un desplazamiento a las coordenadas de la trayectoria del trazo o duplicar su ancho y luego enmascarar un lado u otro, porque SVG no solo no admite de forma nativa la alineación del trazo de Illustrator, sino que PostScript tampoco .

La especificación para trazos en el Manual PostScript de Adobe, 2da edición dice: " 4.5.1 Trazos: el operador de trazo dibuja una línea de cierto grosor a lo largo de la ruta actual. Para cada segmento recto o curvo en el camino, el trazo dibuja una línea centrada en el segmento con lados paralelos al segmento ". (énfasis suyo)

El resto de la especificación no tiene atributos para compensar la posición de la línea. Cuando Illustrator le permite alinearse dentro o fuera, vuelve a calcular el desplazamiento de la ruta real (porque todavía es computacionalmente más barato que la sobreimpresión y luego el enmascaramiento). Las coordenadas de ruta en el documento .ai son referencia, no lo que se rastrilla o exporta a un formato final.

Debido a que el formato nativo de Inkscape es SVG de especificaciones, no puede ofrecer una característica que carece de especificaciones.

usuario1574945
fuente
4

Aquí hay una solución para el rect uso de límites internossymbol yuse .

Ejemplo : https://jsbin.com/yopemiwame/edit?html,output

SVG :

<svg>
  <symbol id="inner-border-rect">
    <rect class="inner-border" width="100%" height="100%" style="fill:rgb(0,255,255);stroke-width:10;stroke:rgb(0,0,0)">
  </symbol>
  ...
  <use xlink:href="#inner-border-rect" x="?" y="?" width="?" height="?">
</svg>

Nota: Asegúrese de reemplazar el ?enuse los valores reales.

Antecedentes : La razón por la que esto funciona es porque el símbolo establece una nueva ventana gráfica mediante la sustitución symbolcon svgy la creación de un elemento en el DOM sombra. Esto svgde la sombra DOM se vincula a su SVGelemento actual . Tenga en cuenta que svgs puede anidarse y cada uno svgcrea una nueva ventana gráfica, que recorta todo lo que se superpone, incluido el borde superpuesto. Para obtener una descripción mucho más detallada de lo que está sucediendo, lea este fantástico artículo de Sara Soueidan.

F Lekschas
fuente
2

No sé qué tan útil será eso, pero en mi caso simplemente creé otro círculo con borde solamente y lo coloqué "dentro" de la otra forma.

El testigo
fuente
0

Una posible solución (sucia) es mediante el uso de patrones,

Aquí hay un ejemplo con un triángulo interior:

https://jsfiddle.net/qr3p7php/5/

<style>
#triangle1{
  fill: #0F0;
  fill-opacity: 0.3;
  stroke: #000;
  stroke-opacity: 0.5;
  stroke-width: 20;
}
#triangle2{
  stroke: #f00;
  stroke-opacity: 1;
  stroke-width: 1;
}    
</style>

<svg height="210" width="400" >
    <pattern id="fagl" patternUnits="objectBoundingBox" width="2" height="1" x="-50%">
        <path id="triangle1" d="M150 0 L75 200 L225 200 Z">
    </pattern>    
    <path id="triangle2" d="M150 0 L75 200 L225 200 Z" fill="url(#fagl)"/>
</svg>
max-lt
fuente
0

La solución de Xavier Ho de duplicar el ancho del trazo y cambiar el orden de la pintura es brillante, aunque solo funciona si el relleno es de color sólido, sin transparencia.

He desarrollado otro enfoque, más complicado pero funciona para cualquier relleno. También funciona en elipses o caminos (con el último hay algunos casos de esquina con comportamiento extraño, por ejemplo, caminos abiertos que se cruzan entre sí, pero no mucho).

El truco es mostrar la forma en dos capas. Uno sin trazo (solo relleno) y otro solo con trazo de doble ancho (relleno transparente) y pasado a través de una máscara que muestra la forma completa, pero oculta la forma original sin trazo.

  <svg width="240" height="240" viewBox="0 0 1024 1024">
  <defs>
    <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
    <mask id="mask">
      <use xlink:href="#ld" stroke="#FFFFFF" stroke-width="160" fill="#FFFFFF"/>
      <use xlink:href="#ld" fill="#000000"/>
    </mask>
  </defs>
  <g>
    <use xlink:href="#ld" fill="#00D2B8"/>
    <use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="red" mask="url(#mask)"/>
  </g>
  </svg>
hirunatan
fuente