Pouvez-vous contrôler la façon dont la largeur de trait d'un SVG est dessinée?

204

Actuellement en construction d'une application SVG basée sur un navigateur. Dans cette application, différentes formes peuvent être stylisées et positionnées par l'utilisateur, y compris des rectangles.

Lorsque j'applique un stroke-widthà un rectélément SVG , par exemple 1px, le trait est appliqué au rectdécalage et à l'encart de de différentes manières par différents navigateurs. Cela s'avère problématique, surtout lorsque j'essaie de calculer la largeur extérieure et la position visuelle d'un rectangle et de le positionner à côté d'autres éléments.

Par exemple:

  • Firefox ajoute un encart de 1 px (en bas et à gauche) et un décalage de 1 px (en haut et à droite)
  • Chrome ajoute un encart de 1 px (en haut et à gauche) et un décalage de 1 px (en bas et à droite)

Jusqu'à présent, ma seule solution serait de dessiner moi-même les bordures réelles (probablement avec l' pathoutil) et de positionner les bordures derrière l'élément tracé. Mais cette solution est une solution de contournement désagréable, et je préférerais ne pas emprunter cette voie si possible.

Donc ma question est, pouvez-vous contrôler comment un SVG stroke-widthest dessiné sur des éléments?

Steve
la source
il y a des hacks de filtre que vous pouvez utiliser pour y parvenir - mais ce n'est pas une excellente solution
Michael Mullany
2
Il y a le paint-orderparamètre, où vous pouvez spécifier, que le remplissage doit être rendu au-dessus du trait, donc vous obtiendrez «l'alignement extérieur», voir jsfiddle.net/hne0kyLg/1
Ivan Kuckir
A trouvé un moyen de le faire en utilisant les attributs css «contour-»: codepen.io/badcat/pen/YVzmYY . Je ne sais pas quel est le support pour cela dans les navigateurs, mais cela pourrait être utile.
bplmp

Réponses:

375

Non, vous ne pouvez pas spécifier si le trait est dessiné à l'intérieur ou à l'extérieur d'un élément. J'ai fait une proposition au groupe de travail SVG pour cette fonctionnalité en 2003, mais elle n'a reçu aucun soutien (ou discussion).

SVG a proposé un exemple d'emplacement de trait, de phrogz.net/SVG/stroke-location.svg

Comme je l'ai noté dans la proposition,

  • vous pouvez obtenir le même résultat visuel que "à l'intérieur" en doublant la largeur de votre trait, puis en utilisant un tracé de détourage pour attacher l'objet à lui-même, et
  • vous pouvez obtenir le même résultat visuel que «extérieur» en doublant la largeur du trait, puis en superposant une copie sans trait de l'objet au-dessus de lui-même.

Edit : Cette réponse peut être erronée à l'avenir. Il devrait être possible d'obtenir ces résultats en utilisant des effets vectoriels SVG , en combinant veStrokePathavec veIntersect(pour «à l'intérieur») ou avec veExclude(pour «à l'extérieur). Cependant, les effets vectoriels sont toujours un projet de module de travail sans implémentations que je puisse encore trouver.

Edit 2 : Le projet de spécification SVG 2 comprend une stroke-alignmentpropriété (avec centre | à l'intérieur | en dehors des valeurs possibles). Cette propriété peut éventuellement devenir un UA.

Edit 3 : De manière amusante et décevante, le groupe de travail SVG a supprimé stroke-alignmentde SVG 2. Vous pouvez voir certaines des préoccupations décrites après la prose ici .

Phrogz
la source
2
J'ai pensé que cela pourrait être le cas. Pour résoudre ce problème, j'ai écrit une fonction wrapper pour getBBox () appelée getStrokedBBox (). Ce wrapper renvoie la BBox en fonction de la façon dont le navigateur applique des traits à l'encart et au décalage d'une forme. Ce n'est pas parfait (besoin de continuer à vérifier par rapport aux dernières versions du navigateur) mais il fournit avec précision une largeur extérieure de formes pour l'instant.
Steve
6
@Phrogz nous le verrons peut-être avant 10 ans. svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint annotation
frenchone
1
C'est enfin là! Pour ma part, j'ai vraiment insisté pour que cette propriété arrive.
1er
J'ai créé un script svg-contour pour tracer le contour de n'importe quel SVGGeometryElement qui peut être utilisé comme solution de contournement de l'alignement des traits, pour toute personne intéressée, vous pouvez trouver une description ici
maioman
2
Y a-t-il une page où je peux voter pour la proposition? Merci. Semble ridicule, il n'est pas pris en charge.
mahish
57

MISE À JOUR: L' stroke-alignmentattribut a été déplacé le 1er avril 2015 vers une toute nouvelle spécification appelée SVG Strokes .

À partir du projet de rédaction SVG 2.0 du 26 février 2015 (et peut-être depuis le 13 février ),la stroke-alignmentpropriété est présenteavec les valeurs inner, center (par défaut) et outer.

Il semble fonctionner de la même manière que la stroke-locationpropriété proposée par @Phrogz et la stroke-positionsuggestion ultérieure . Cette propriété est prévue depuis au moins 2011, mais à part une annotation qui dit

SVG 2 doit inclure un moyen de spécifier la position de la course

, il n'a jamais été détaillé dans les spécifications car il a été reporté - jusqu'à présent, semble-t-il.

Aucun navigateur ne prend en charge cette propriété, ou, pour autant que je sache, aucune des nouvelles fonctionnalités de SVG 2 pour le moment, mais j'espère qu'elles le seront bientôt à mesure que les spécifications mûriront. C'est une propriété que je réclame personnellement d'avoir, et je suis vraiment heureux qu'elle soit enfin là dans la spécification.

Il semble y avoir quelques problèmes quant à la façon dont la propriété doit se comporter sur les chemins ouverts ainsi que sur les boucles. Ces problèmes prolongeront très probablement les implémentations sur les navigateurs. Cependant, je mettrai à jour cette réponse avec de nouvelles informations à mesure que les navigateurs commenceront à prendre en charge cette propriété.

mnsth
la source
1
stroke-alignmentest spécifié dans SVG Strokes, un document de travail du W3C. Pendant ce temps, l'ébauche de l'éditeur SVG 2 W3C indique qu'une propriété de positionnement de trait doit être dans la spécification SVG à svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint , mais cette spécification a déjà atteint le statut de recommandation de candidat W3C et aucune propriété de ce type n'est dans la spécification en dehors d'un lien vers la stroke-positionproposition, ce qui ne semble pas être le cas.
Patrick Dark
47

J'ai trouvé un moyen facile, qui a quelques restrictions, mais a fonctionné pour moi:

  • définir la forme en defs
  • définir un chemin de clip faisant référence à la forme
  • utilisez-le et doublez la course avec l'extérieur

Voici un exemple de travail:

<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
la source
2
La meilleure réponse que j'ai trouvée
Ed Kolosovsky
1
Solution intelligente. Merci
Vince Yuan
Cela devrait être accepté comme réponse. L'utilisation de <defs> et <use> est la solution la plus élégante actuellement disponible.
Nyerguds le
Belle solution. Comment l'inverseriez-vous pour faire un coup extérieur?
Lucent
Pourquoi le haut niveau <use>? Pourquoi ne pas simplement insérer directement le chemin et le chemin du clip? Cela fonctionne très 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>. (Oui, c'est SVG tout à fait raisonnable et correct et restituera correctement partout. Ne craignez aucune récursivité.)
Chris Morgan
30

Vous pouvez utiliser CSS pour styliser l'ordre des traits et des remplissages. C'est-à-dire, caressez d'abord puis remplissez-en deux et obtenez l'effet souhaité.

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

Code CSS:

paint-order: stroke;
Xavier Ho
la source
C'est parfait! Merci pour le partage
BrunoFenzl
1
La documentation liée ne semble pas indiquer si le trait est à l'intérieur, à l'extérieur ou centré? Pourriez-vous préciser comment contrôler où le trait est tracé? Merci.
Crashalot
2
Le trait est centré, comme il l'a toujours été, mais si vous avez un remplissage plein, le réglage de l'ordre de peinture pour peindre le trait en premier a pour effet de recouvrir la moitié intérieure du trait, ce qui a le même effet que de dessiner un course à moitié aussi épaisse à l'extérieur.
Chris Morgan
7

Voici une fonction qui calculera le nombre de pixels que vous devez ajouter - en utilisant le trait donné - en haut, à droite, en bas et à gauche, tous basés sur le navigateur:

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
la source
6

Comme les personnes ci-dessus l'ont noté, vous devrez soit recalculer un décalage par rapport aux coordonnées du tracé du trait, soit doubler sa largeur, puis masquer un côté ou l'autre, car non seulement le SVG ne prend pas en charge nativement l'alignement du trait d'Illustrator, mais PostScript ne le fait pas non plus .

La spécification des traits dans le manuel Adobe PostScript 2e édition indique: " 4.5.1 Trait: L' opérateur de traits dessine une ligne d'une certaine épaisseur le long du chemin courant. Pour chaque segment droit ou courbe du chemin, le trait dessine une ligne centrée sur le segment dont les côtés sont parallèles au segment. " (mettre l'accent sur le leur)

Le reste de la spécification n'a pas d'attributs pour compenser la position de la ligne. Lorsqu'Illustrator vous permet de vous aligner à l'intérieur ou à l'extérieur, il recalcule le décalage du chemin réel (car il est toujours moins cher en calcul que la surimpression puis le masquage). Les coordonnées du chemin dans le document .ai sont des références, pas ce qui est tramé ou exporté vers un format final.

Comme le format natif d'Inkscape est une spécification SVG, il ne peut pas offrir une fonctionnalité qui manque à la spécification.

user1574945
la source
4

Voici un travail autour de l' intérieur bordé en rect utilisant symbolet use.

Exemple : 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>

Remarque: Assurez - vous de remplacer le ?dans des usevaleurs réelles.

Contexte : La raison pour laquelle cela fonctionne est parce que le symbole établit une nouvelle fenêtre en remplaçant symbolavec svget en créant un élément dans le DOM d'ombre. Celui-ci svgdu DOM fantôme est ensuite lié à votre SVGélément actuel . Notez que les svgs peuvent être imbriqués et chacun svgcrée une nouvelle fenêtre, qui coupe tout ce qui se chevauche, y compris la bordure qui se chevauche. Pour un aperçu beaucoup plus détaillé de ce qui se passe, lisez ce fantastique article de Sara Soueidan.

F Lekschas
la source
2

Je ne sais pas à quel point cela sera utile, mais dans mon cas, je viens de créer un autre cercle avec bordure uniquement et de le placer «à l'intérieur» de l'autre forme.

Le témoin
la source
0

Une solution (sale) possible consiste à utiliser des modèles,

voici un exemple avec un triangle intérieur barré:

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
la source
0

La solution de Xavier Ho de doubler la largeur du trait et de changer l'ordre de peinture est brillante, mais ne fonctionne que si le remplissage est de couleur unie, sans transparence.

J'ai développé une autre approche, plus compliquée mais qui fonctionne pour n'importe quel remplissage. Il fonctionne également dans les ellipses ou les chemins (avec les derniers, il y a des cas d'angle avec un comportement étrange, par exemple des chemins ouverts qui se croisent, mais pas beaucoup).

L'astuce consiste à afficher la forme en deux couches. Un sans contour (remplissage uniquement) et un autre uniquement avec contour à double largeur (remplissage transparent) et passé à travers un masque qui montre la forme entière, mais masque la forme d'origine sans contour.

  <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
la source