Czy możesz kontrolować, w jaki sposób rysowana jest szerokość obrysu SVG?

204

Obecnie budujemy aplikację SVG opartą na przeglądarce. W tej aplikacji użytkownik może stylizować i ustawiać różne kształty, w tym prostokąty.

Kiedy zastosuję a stroke-widthdo rectpowiedzmy elementu SVG 1px, obrys jest stosowany rectw różny sposób do przesunięcia i wstawki przez różne przeglądarki. Jest to kłopotliwe, zwłaszcza gdy próbuję obliczyć zewnętrzną szerokość i wizualną pozycję prostokąta i ustawić go obok innych elementów.

Na przykład:

  • Firefox dodaje wstawkę 1px (dolny i lewy) oraz przesunięcie 1px (górny i prawy)
  • Chrome dodaje wstawkę 1px (u góry i po lewej) i przesunięcie 1px (u dołu i po prawej)

Moim jedynym rozwiązaniem do tej pory było narysowanie rzeczywistych granic (prawdopodobnie za pomocą pathnarzędzia) i umieszczenie granic za obrysowanym elementem. Ale to rozwiązanie jest nieprzyjemnym obejściem i wolałbym nie iść tą drogą, jeśli to możliwe.

Moje pytanie brzmi: czy możesz kontrolować sposób stroke-widthrysowania pliku SVG na elementach?

Steve
źródło
istnieją hacki filtracyjne, których można użyć do osiągnięcia tego - ale to nie jest świetne rozwiązanie
Michael Mullany,
2
Istnieje paint-orderparametr, w którym można określić, że wypełnienie powinno być renderowane na szczycie obrysu, aby uzyskać „wyrównanie zewnętrzne”, patrz jsfiddle.net/hne0kyLg/1
Ivan Kuckir
Znalazłem sposób, aby to zrobić za pomocą atrybutów css „zarys-”: codepen.io/badcat/pen/YVzmYY . Nie jestem pewien, jakie jest to wsparcie w różnych przeglądarkach, ale może być przydatne.
bplmp,

Odpowiedzi:

375

Nie, nie można określić, czy obrys ma być rysowany wewnątrz czy na zewnątrz elementu. Złożyłem propozycję grupie roboczej SVG dotyczącą tej funkcji w 2003 r., Ale nie otrzymała ona żadnego wsparcia (ani dyskusji).

SVG zaproponował przykład lokalizacji udaru mózgu z phrogz.net/SVG/stroke-location.svg

Jak zauważyłem we wniosku,

  • możesz uzyskać taki sam efekt wizualny jak „wewnątrz”, podwajając szerokość obrysu, a następnie używając ścieżki przycinającej, aby przyciąć obiekt do siebie, oraz
  • możesz uzyskać taki sam efekt wizualny jak „na zewnątrz”, podwajając szerokość obrysu, a następnie nakładając na siebie kopię obiektu bez obrysu.

Edycja : Ta odpowiedź może być błędna w przyszłości. Osiągnięcie tych wyników powinno być możliwe przy użyciu SVG Effects Vector , poprzez połączenie veStrokePathz veIntersect(dla „wewnątrz”) lub z veExclude(dla „na zewnątrz). Jednak Vector Effects są nadal działającym modułem roboczym, bez żadnych implementacji, które mogę jeszcze znaleźć.

Edycja 2 : Specyfikacja szkicu SVG 2 zawiera stroke-alignmentwłaściwość (ze środkiem | wewnątrz | poza możliwymi wartościami). Ta właściwość może ostatecznie przekształcić się w UA.

Edycja 3 : Amusingly i dissapointingly grupa robocza SVG usunęła stroke-alignmentz SVG 2. Można zobaczyć niektóre z problemów opisanych po prozę tutaj .

Phrogz
źródło
2
Tak może być. Aby rozwiązać ten problem, napisałem funkcję otoki dla getBBox () o nazwie getStrokedBBox (). To opakowanie zwraca BBox zgodnie z tym, jak przeglądarka stosuje obrysy do wstawki i odsunięcia kształtu. Nie jest idealny (trzeba sprawdzać najnowsze wersje przeglądarki), ale na razie dokładnie zapewnia szerokość zewnętrzną kształtów.
Steve,
6
@Progrog może zobaczymy to przed upływem 10 lat. svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint adnotacja
francuskione
1
W końcu jest tutaj! Ja, na przykład, naprawdę nalegałem na przybycie tej nieruchomości.
mnst
Stworzyłem skrypt svg-contour do śledzenia konturu dowolnego SVGGeometryElement, który może być użyty jako obejście wyrównania uderzeń, dla każdego zainteresowanego możesz znaleźć opis tutaj
maioman,
2
Czy jest jakaś strona, na której mogę głosować za propozycją? Dzięki. To śmieszne, że nie jest obsługiwane.
mahish
57

UPDATE:stroke-alignment atrybut był w dniu 1 kwietnia 2015 roku przeniósł się do zupełnie nowej specyfikacji zwanej Strokes SVG .

Od wersji redakcyjnej SVG 2.0 z 26 lutego 2015 r. (I być może od 13 lutego ),stroke-alignmentnieruchomość jest obecnyz wartościami inner, center (domyślnie) i outer.

Wygląda na to, że działa w taki sam sposób, jak stroke-locationwłaściwość zaproponowana przez @Phrogz i późniejszą stroke-positionsugestię . Ta nieruchomość została zaplanowana co najmniej od 2011 r., Ale oprócz wspomnianej adnotacji

SVG 2 powinien zawierać sposób określenia położenia skoku

, nigdy nie został szczegółowo opisany, ponieważ został odroczony - wydaje się, że do tej pory.

Żadna przeglądarka nie obsługuje tej właściwości ani, o ile mi wiadomo, żadnej z nowych funkcji SVG 2, ale mam nadzieję, że wkrótce będą dostępne. To była własność, o którą osobiście namawiałem i bardzo się cieszę, że w końcu jest w specyfikacji.

Wydaje się, że istnieją pewne problemy dotyczące zachowania właściwości na otwartych ścieżkach i pętlach. Problemy te najprawdopodobniej przedłużą wdrożenia w różnych przeglądarkach. Jednak zaktualizuję tę odpowiedź o nowe informacje, gdy przeglądarki zaczną obsługiwać tę właściwość.

mnsth
źródło
1
stroke-alignmentjest określony w SVG Strokes, roboczym szkicu W3C. Tymczasem wersja robocza edytora W3C SVG 2 mówi, że właściwość pozycjonowania obrysu powinna znajdować się w specyfikacji SVG na svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint , ale ta specyfikacja już osiągnęła status Rekomendacji kandydata W3C i żadna taka właściwość nie jest w specyfikacji oprócz linku do stroke-positionwniosku, co wydaje się, że tak nie będzie.
Patrick Dark
47

Znalazłem prosty sposób, który ma kilka ograniczeń, ale działał dla mnie:

  • zdefiniuj kształt w definicji
  • zdefiniuj ścieżkę klipu odnoszącą się do kształtu
  • użyj go i dwukrotnie pociągnij, gdy zewnętrzna część jest obcięta

Oto działający przykład:

<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
źródło
2
Najlepsza odpowiedź, jaką znalazłem
Ed Kolosovsky,
1
Sprytne rozwiązanie. Dzięki
Vince Yuan,
Należy to zaakceptować jako odpowiedź. Korzystanie z <defs> i <use> jest najbardziej eleganckim obecnie dostępnym rozwiązaniem.
Nyerguds
Niezłe rozwiązanie. Jak zrobiłbyś to odwrotnie, by wykonać obrys zewnętrzny?
Lucent
Dlaczego najwyższy poziom <use>? Dlaczego nie wstawić bezpośrednio ścieżki i ścieżki przycinania? Działa to dobrze: <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>. (Tak, jest to całkowicie uzasadnione i poprawne SVG i będzie renderowane poprawnie wszędzie. Nie obawiaj się rekurencji.)
Chris Morgan
30

Możesz użyć CSS do stylizacji kolejności obrysu i wypełnień. Oznacza to, że najpierw pociągnij, a następnie wypełnij drugi i uzyskaj pożądany efekt.

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

Kod CSS:

paint-order: stroke;
Xavier Ho
źródło
To jest doskonałe! Dzięki za udostępnienie
BrunoFenzl,
1
Powiązana dokumentacja nie wydaje się wskazywać, czy udar jest wewnątrz, na zewnątrz czy wyśrodkowany? Czy możesz wyjaśnić, jak kontrolować, gdzie jest rysowany obrys? Dzięki.
Crashalot
2
Obrys jest wyśrodkowany, jak zawsze - ale jeśli masz pełne wypełnienie, efektem dostosowania kolejności malowania do obrysu najpierw jest zamalowanie wewnętrznej połowy obrysu, co ma taki sam efekt jak narysowanie pociągnięcie na pół grubości jak na zewnątrz.
Chris Morgan
7

Oto funkcja, która obliczy, ile pikseli należy dodać - przy użyciu danego obrysu - do góry, z prawej, u dołu i z lewej strony, wszystkie na podstawie przeglądarki:

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
źródło
6

Jak zauważyli ludzie powyżej, albo będziesz musiał ponownie obliczyć przesunięcie współrzędnych ścieżki obrysu lub podwoić jego szerokość, a następnie zamaskować jedną lub drugą stronę, ponieważ SVG nie tylko natywnie nie obsługuje wyrównania obrysu programu Illustrator, ale PostScript również nie .

Specyfikacja uderzeń w PostScript ręcznych stanach Wydanie 2nd Adobe: " 4.5.1 Głaskanie: udar operator rysuje linię pewnej grubości wzdłuż toru prądowego Dla każdego segmentu prostym lub zakrzywionym w ścieżce. Udar rysuje linię, który jest skoncentrowany na segment z bokami równoległymi do segmentu. ” (podkreślenie ich)

Pozostała część specyfikacji nie ma atrybutów do przesunięcia pozycji linii. Kiedy program Illustrator pozwala wyrównać do wewnątrz lub na zewnątrz, ponownie oblicza przesunięcie rzeczywistej ścieżki (ponieważ jest to nadal tańsze obliczeniowo niż nadruk, a następnie maskowanie). Współrzędne ścieżki w dokumencie .ai są odniesieniem, a nie tym, co zostaje rastrowane lub eksportowane do ostatecznego formatu.

Ponieważ rodzimym formatem Inkscape jest specyfikacja SVG, nie może oferować funkcji, której brakuje w specyfikacji.

użytkownik1574945
źródło
4

Oto obejście dla wewnętrznych granic rect za pomocą symboli use.

Przykład : 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>

Uwaga: pamiętaj, aby zastąpić ?in userzeczywistymi wartościami.

Tło : Powodem dlaczego to działa, bo jest symbolem ustanawia nową rzutnię zastępując symbolze svgi tworzenia elementu w DOM cienia. Ten svgcień DOM jest następnie łączony z bieżącym SVGelementem. Zauważ, że svgs może być zagnieżdżone i każda svgtworzy nową rzutnię, która przycina wszystko, co nakłada się, w tym nakładającą się ramkę. Aby uzyskać bardziej szczegółowy przegląd tego, co się dzieje, przeczytaj fantastyczny artykuł Sary Soueidan.

F Lekschas
źródło
2

Nie wiem, jak to będzie pomocne, ale w moim przypadku właśnie utworzyłem inny okrąg z samą ramką i umieściłem go „wewnątrz” drugiego kształtu.

Świadek
źródło
0

(Brudnym) możliwym rozwiązaniem jest użycie wzorów,

oto przykład z obrysowanym trójkątem:

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
źródło
0

Rozwiązanie Xaviera Ho, polegające na podwojeniu szerokości obrysu i zmianie kolejności farb, jest genialne, chociaż działa tylko wtedy, gdy wypełnienie ma jednolity kolor, bez przezroczystości.

Opracowałem inne podejście, bardziej skomplikowane, ale działa na każde wypełnienie. Działa również w elipsach lub ścieżkach (z późniejszymi są niektóre przypadki narożne o dziwnym zachowaniu, na przykład otwarte ścieżki, które przecinają się, ale niewiele).

Sztuką jest wyświetlanie kształtu na dwóch warstwach. Jeden bez obrysu (tylko wypełnienie), a drugi tylko z obrysem o podwójnej szerokości (przezroczyste wypełnienie) i przeszedł przez maskę, która pokazuje cały kształt, ale ukrywa oryginalny kształt bez obrysu.

  <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
źródło