Puoi controllare come viene disegnata la larghezza del tratto di un SVG?


204

Attualmente in costruzione un'applicazione SVG basata su browser. All'interno di questa app, le varie forme possono essere disegnate e posizionate dall'utente, inclusi i rettangoli.

Quando applico stroke-widtha un rectelemento SVG di dire 1px, il tratto viene applicato rectall'offset e all'inserzione in diversi modi da diversi browser. Ciò si sta rivelando problematico, specialmente quando provo a calcolare la larghezza esterna e la posizione visiva di un rettangolo e posizionarlo accanto ad altri elementi.

Per esempio:

  • Firefox aggiunge 1px inserto (in basso e a sinistra) e 1px offset (in alto e a destra)
  • Chrome aggiunge 1px inserto (in alto e a sinistra) e 1px offset (in basso e a destra)

La mia unica soluzione finora sarebbe quella di disegnare personalmente i bordi (probabilmente con lo pathstrumento) e posizionare i bordi dietro l'elemento tracciato. Ma questa soluzione è una soluzione spiacevole e preferirei non seguire questa strada, se possibile.

Quindi la mia domanda è: puoi controllare come stroke-widthviene disegnato un SVG sugli elementi?


ci sono hack di filtri che puoi usare per raggiungere questo obiettivo - ma non è un'ottima soluzione
Michael Mullany,

2
Esiste il paint-orderparametro, dove è possibile specificare, che il riempimento deve essere eseguito al di sopra del tratto, in modo da ottenere "l'allineamento esterno", vedere jsfiddle.net/hne0kyLg/1
Ivan Kuckir,

Trovato un modo per farlo usando gli attributi ' outline- ' di css: codepen.io/badcat/pen/YVzmYY . Non sono sicuro di quale sia il supporto per questo attraverso i browser, ma potrebbe essere utile.
bplmp,

Risposte:


375

No, non è possibile specificare se il tratto viene disegnato all'interno o all'esterno di un elemento. Ho fatto una proposta al gruppo di lavoro SVG per questa funzionalità nel 2003, ma non ha ricevuto supporto (o discussione).

SVG ha proposto un esempio di ictus, da phrogz.net/SVG/stroke-location.svg

Come ho notato nella proposta,

  • puoi ottenere lo stesso risultato visivo di "interno" raddoppiando la larghezza del tratto e quindi usando un tracciato di ritaglio per agganciare l'oggetto a se stesso e
  • puoi ottenere lo stesso risultato visivo di "fuori" raddoppiando la larghezza del tratto e quindi sovrapponendo una copia senza tratto dell'oggetto sopra se stesso.

Modifica : questa risposta potrebbe essere errata in futuro. Dovrebbe essere possibile ottenere questi risultati usando SVG Vector Effects , combinando veStrokePathcon veIntersect(per "dentro") o con veExclude(per "fuori"). Tuttavia, Vector Effects è ancora un modulo di bozza funzionante senza implementazioni che posso ancora trovare.

Modifica 2 : la specifica bozza SVG 2 include una stroke-alignmentproprietà (con centro | dentro | fuori dai possibili valori). Questa proprietà potrebbe trasformarsi in UA alla fine.

Modifica 3 : In modo divertente e dissapointingly, il gruppo di lavoro SVG ha rimosso stroke-alignmentda SVG 2. Potete vedere alcune delle preoccupazioni descritte dopo la prosa qui .


2
Pensato che potrebbe essere il caso. Per risolvere questo ho scritto una funzione wrapper per getBBox () chiamata getStrokedBBox (). Questo wrapper restituisce BBox in base al modo in cui il browser applica i tratti all'inserzione e all'offset di una forma. Non è perfetto (è necessario continuare a controllare rispetto alle ultime versioni del browser) ma per ora fornisce una larghezza esterna delle forme accurata.
Steve,

6
@Phrogz forse lo vedremo prima che siano trascorsi 10 anni. svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint annotazione
frenchone

1
È finalmente arrivato! Io, per esempio, ho davvero sollecitato l'arrivo di questa proprietà.
mnsth

Ho creato uno script svg-contour per tracciare il contorno di qualsiasi elemento SVGGeometryElement che può essere usato come soluzione alternativa per l'allineamento del tratto, per chiunque sia interessato puoi trovare una descrizione qui
maioman

2
Esiste una pagina in cui posso votare per la proposta? Grazie. Sembra ridicolo che non è supportato.
Mahish,

57

AGGIORNAMENTO: l' stroke-alignmentattributo è stato spostato il 1 ° aprile 2015 in una specifica completamente nuova chiamata SVG Strokes .

A partire dalla bozza dell'editor SVG 2.0 del 26 febbraio 2015 (e possibilmente dal 13 febbraio ),la stroke-alignmentproprietà è presentecon i valori inner, center (impostazione predefinita) e outer.

Sembra funzionare allo stesso modo della stroke-locationproprietà proposta da @Phrogz e dal stroke-positionsuggerimento successivo . Questa proprietà è stata pianificata almeno dal 2011, ma a parte un'annotazione che ha detto

SVG 2 deve includere un modo per specificare la posizione della corsa

, non è mai stato dettagliato nelle specifiche in quanto è stato rinviato - fino ad ora, sembra.

Nessun browser supporta questa proprietà o, per quanto ne so, una delle nuove funzionalità di SVG 2, ma spero che presto le specifiche matureranno. Questa è stata una proprietà che personalmente ho sollecitato ad avere, e sono davvero felice che sia finalmente lì nelle specifiche.

Sembra che ci siano alcuni problemi su come la proprietà dovrebbe comportarsi su percorsi aperti e loop. Questi problemi, molto probabilmente, prolungheranno le implementazioni tra i browser. Tuttavia, aggiornerò questa risposta con nuove informazioni non appena i browser inizieranno a supportare questa proprietà.


1
stroke-alignmentè specificato in SVG Strokes, una bozza di lavoro W3C. Nel frattempo, la bozza dell'editor SVG 2 W3C afferma che una proprietà di posizionamento della corsa deve essere nella specifica SVG su svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint , ma tale specifica ha già raggiunto lo stato di Raccomandazione candidato W3C e nessuna proprietà è nelle specifiche a parte un link alla stroke-positionproposta, facendo sembrare che non sarà il caso.
Patrick Dark,

47

Ho trovato un modo semplice, che ha alcune restrizioni, ma ha funzionato per me:

  • definire la forma in def
  • definire un tracciato di ritaglio che fa riferimento alla forma
  • usalo e raddoppia il tratto mentre l'esterno è tagliato

Ecco un esempio funzionante:

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


2
La migliore risposta che ho trovato
Ed Kolosovsky, l'

1
Soluzione intelligente. Grazie
Vince Yuan

Questo dovrebbe essere accettato come risposta. L'uso di <defs> e <use> è la soluzione più elegante attualmente disponibile.
Nyerguds,

Bella soluzione. Come lo invertiresti per fare un colpo esterno?
Lucent,

Perché il massimo livello <use>? Perché non inserire semplicemente il percorso e il tracciato clip direttamente? Questo funziona bene: <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ì, questo è SVG completamente ragionevole e corretto e verrà visualizzato correttamente ovunque. Non temere ricorsioni.)
Chris Morgan,

30

Puoi usare i CSS per dare uno stile all'ordine di tratti e riempimenti. Cioè, prima traccia e poi riempi secondo, e ottieni l'effetto desiderato.

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

Codice CSS:

paint-order: stroke;

Questo è perfetto! Grazie per aver condiviso
BrunoFenzl,

1
La documentazione collegata non sembra indicare se il tratto sia interno, esterno o centrato? Potresti chiarire come controllare dove viene disegnato il tratto? Grazie.
Crashalot,

2
Il tratto è centrato, come sempre, ma se si ha un riempimento solido, l'effetto di regolare l'ordine di pittura per dipingere prima è che la metà interna del tratto è dipinta, il che ha lo stesso effetto del disegno di un corsa mezza spessa all'esterno.
Chris Morgan,

7

Ecco una funzione che calcolerà quanti pixel devi aggiungere - usando il tratto dato - in alto, a destra, in basso e a sinistra, tutti basati sul browser:

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
            };

        }

    };

6

Come le persone sopra hanno notato, dovrete o ricalcolare un offset rispetto alle coordinate del tracciato del tratto o raddoppiarne la larghezza e quindi mascherare un lato o l'altro, perché non solo SVG non supporta in modo nativo l'allineamento del tratto di Illustrator, ma PostScript non .

Le specifiche per i tratti nel Manuale Adobe PostScript 2a edizione indicano: " 4.5.1 Tratto: il operatore tratto disegna una linea di uno spessore lungo il percorso corrente. Per ogni segmento diritto o curvo nel percorso, il tratto disegna una linea centrata su il segmento con lati paralleli al segmento ". (enfatizza la loro)

Il resto della specifica non ha attributi per compensare la posizione della linea. Quando Illustrator ti consente di allinearti all'interno o all'esterno, sta ricalcolando l'offset del percorso effettivo (perché è ancora più economico dal punto di vista computazionale rispetto alla sovrastampa e al mascheramento). Le coordinate del percorso nel documento .ai sono riferimenti, non ciò che viene rasterizzato o esportato in un formato finale.

Poiché il formato nativo di Inkscape è in formato SVG, non può offrire una funzione che manca alle specifiche.


4

Ecco una soluzione per i bordi interni rect usando symbole use.

Esempio : 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: assicurarsi di sostituire ?in usecon valori reali.

Sfondo : Il motivo per cui funziona è perché il simbolo stabilisce un nuovo viewport sostituendolo symbolcon svge creando un elemento nel DOM shadow. Questo svgDOM dell'ombra viene quindi collegato al tuo SVGelemento corrente . Si noti che svgè possibile nidificare e ognuno svgcrea una nuova finestra, che ritaglia tutto ciò che si sovrappone, incluso il bordo sovrapposto. Per una panoramica molto più dettagliata di ciò che sta succedendo leggi questo fantastico articolo di Sara Soueidan.


2

Non so quanto sarà utile, ma nel mio caso ho appena creato un altro cerchio con solo bordo e l'ho posizionato "dentro" l'altra forma.


0

Una soluzione (sporca) possibile è usando schemi,

ecco un esempio con un triangolo tratteggiato all'interno:

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

La soluzione di Xavier Ho di raddoppiare l'ampiezza della pennellata e cambiare l'ordine di verniciatura è brillante, sebbene funzioni solo se il riempimento è un colore solido, senza trasparenza.

Ho sviluppato un altro approccio, più complicato ma funziona per qualsiasi riempimento. Funziona anche con ellissi o percorsi (in seguito ci sono alcuni casi angolari con un comportamento strano, ad esempio percorsi aperti che si incrociano, ma non molto).

Il trucco è visualizzare la forma in due livelli. Uno senza tratto (solo riempimento) e un altro solo con tratto a doppia larghezza (riempimento trasparente) e passato attraverso una maschera che mostra l'intera forma, ma nasconde la forma originale senza tratto.

  <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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.