web-dev-qa-db-fra.com

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

Actuellement en train de construire une application SVG basée sur un navigateur. Dans cette application, l'utilisateur peut styler et positionner diverses formes, notamment des rectangles.

Lorsque j'applique un stroke-width à un élément SVG rect de dire 1px, le trait est appliqué au décalage et à l'insertion de la rect de différentes manières par différents navigateurs. Cela s'avère gênant, 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 incrément de 1px (en bas et à gauche) et un décalage de 1px (en haut et à droite)
  • Chrome ajoute un incrément de 1px (en haut et à gauche) et un décalage de 1px (en bas et à droite)

Ma seule solution à ce jour serait de tracer moi-même les frontières réelles (probablement avec l'outil path) et de placer les frontières derrière l'élément en trait. Mais cette solution est une solution de contournement désagréable, et je préférerais ne pas aller dans cette voie si possible.

Ma question est donc la suivante: pouvez-vous contrôler la manière dont le stroke-width du SVG est dessiné sur des éléments?

169
Steve

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 soumis une proposition au groupe de travail SVG pour cette fonctionnalité en 2003, mais ce dernier n'a reçu aucun soutien (ni discussion).

SVG proposed stroke-location example, from 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.

Edit: Cette réponse pourrait être fausse dans le futur. Il devrait être possible d'obtenir ces résultats avec SVG Vector Effects , en combinant veStrokePath avec veIntersect (pour 'à l'intérieur') ou avec veExclude (pour 'à l'extérieur). Cependant, Vector Effects est toujours un brouillon de travail sans aucune implémentation que je puisse encore trouver.

Edit 2: le projet de spécification SVG 2 comprend une propriété stroke-alignment (avec le centre | dedans | en dehors des valeurs possibles). Cette propriété peut éventuellement devenir un agent utilisateur.

Edit 3: Amusant et décevant, le groupe de travail SVG a supprimé stroke-alignment de SVG 2. Vous pouvez voir certaines des préoccupations décrites après la prose ici .

315
Phrogz

UPDATE: L'attribut stroke-alignmenta été déplacé le 1er avril vers une toute nouvelle spécification appelée SVG Strokes .

Depuis le brouillon de l’éditeur SVG 2.0 du 26 février 2015 (et éventuellement depuis le { 13 février } _), _ { la propriété stroke-alignment est présente avec les valeurs inner, center(par défaut) et outer

Cela semble fonctionner de la même manière que la propriété stroke-location proposée par @Phrogz et que la plus récente suggestion stroke-position . 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 du trait

, il n’a jamais été détaillé dans les spécifications telles qu’elles étaient différées - jusqu’à présent, semble-t-il.

Aucun navigateur ne prend en charge cette propriété, ni, pour autant que je sache, aucune des nouvelles fonctionnalités de SVG 2 pour le moment, mais nous espérons qu'elles le seront bientôt lorsque les spécifications arriveront à maturité. Personnellement, j’ai insisté pour que ce soit un bien, et je suis vraiment heureux que ce soit enfin dans les spécifications.

Il semble y avoir quelques problèmes quant à la manière dont la propriété doit se comporter sur les chemins ouverts ainsi que sur les boucles. Ces problèmes vont probablement prolonger 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é.

49
mnsth

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

  • définir la forme en defs
  • définir un chemin de clip référençant la forme
  • utilisez-le et doublez le trait avec l'extérieur coupé

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>

26
Jorg Janke

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 en fonction du 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
            };

        }

    };
7
Steve

Vous pouvez utiliser CSS pour définir l'ordre des traits et des remplissages. C’est-à-dire que vous commencez par remplir, puis que vous remplissez ensuite, pour obtenir l’effet souhaité.

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

Code CSS:

Paint-order: stroke;
6
Xavier Ho

Comme les personnes ci-dessus l'ont déjà noté, vous devrez recalculer un décalage par rapport aux coordonnées du tracé du trait ou doubler sa largeur, puis masquer un côté ou l'autre, car SVG ne prend pas en charge de manière native l'alignement du trait d'Illustrator, mais PostScript ne le permet . 

La spécification des traits dans le Manuel PostScript d'Adobe, 2e édition, indique: " 4.5.1 Stroking: L'opérateur stroke dessine une ligne d'une certaine épaisseur le long du tracé actuel. segment incurvé dans la trajectoire, stroke trace une ligne qui est centrée sur le segment avec les côtés parallèle au segment. " (emphase les leurs)

Le reste de la spécification n'a aucun attribut permettant de compenser la position de la ligne. Lorsque Illustrator vous permet d’aligner à l’intérieur ou à l’extérieur, il recalcule le décalage du chemin réel (car il reste moins coûteux en calcul que pour la surimpression et le masquage). Les coordonnées de chemin dans le document .ai sont des références, pas ce qui est raster ou exporté dans un format final. 

Le format natif d'Inkscape étant SVG, il ne peut pas offrir une fonctionnalité manquante. 

5
user1574945

Voici une solution de contournement pour inner borderedrect, en utilisant symbol et 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 use par des valeurs réelles.

Background: cela s’explique par le fait que symbol crée une nouvelle fenêtre en remplaçant symbol par svg et en créant un élément dans le DOM fantôme. Cette svg du DOM fantôme est ensuite liée à votre élément SVG actuel. Notez que svgs peut être imbriqué et chaque svg crée une nouvelle fenêtre d'affichage, qui clipse tout ce qui se chevauche, y compris la bordure se chevauchant. Pour un aperçu beaucoup plus détaillé de ce qui se passe, lisez cet article fantastique de Sara Soueidan.

2
F Lekschas

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

0
Seb

Une solution possible (sale) consiste à utiliser des motifs,

voici un exemple avec un triangle à l'intérieur:

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>
0
max-lt