Zephyrnet-logo

SVG-patronen optimaliseren tot hun kleinste formaat

Datum:

Ik heb onlangs een bakstenen muurpatroon gemaakt als onderdeel van mijn #PetitePatterns series, een uitdaging waarbij ik organisch ogende patronen of texturen in SVG maak binnen 560 bytes (of ongeveer de grootte van twee tweets). Om aan deze beperking te voldoen, heb ik een reis gemaakt die me een aantal radicale manieren heeft geleerd om SVG-patronen te optimaliseren, zodat ze zo min mogelijk code bevatten zonder de algehele beeldkwaliteit te beïnvloeden.

Ik wil je door het proces leiden en je laten zien hoe we een SVG-patroon kunnen nemen dat begint bij 197 bytes tot slechts 44 bytes - maar liefst 77.7% reductie!

Het SVG-patroon

CodePen Embed-terugval

Dit is wat een "running bond" baksteenpatroon wordt genoemd. Het is het meest voorkomende baksteenpatroon dat er is, en je hebt het zeker eerder gezien: elke rij stenen is verschoven met de helft van de lengte van een steen, waardoor een zich herhalend verspringend patroon ontstaat. Het arrangement is vrij eenvoudig, het maken van SVG's <pattern> element een perfecte pasvorm om het in code te reproduceren.

de SVG <pattern> element gebruikt een vooraf gedefinieerd grafisch object dat op vaste intervallen langs de horizontale en verticale assen kan worden gerepliceerd (of "tegelen"). In wezen definiëren we een rechthoekig tegelpatroon en dit wordt herhaald om het opvulgebied te schilderen.

Laten we eerst de afmetingen van een steen en de opening tussen elke steen instellen. Laten we voor de eenvoud schone, ronde getallen gebruiken: een breedte van 100 en een hoogte van 30 voor de baksteen, en 10 voor de horizontale en verticale openingen ertussen.

Een gemarkeerd gedeelte van een bakstenen muurpatroon weergeven, het voorbeeld dat we gebruiken voor het optimaliseren van SVG-patronen.

Vervolgens moeten we onze "basis" -tegel identificeren. En met "tegel" heb ik het over patroontegels in plaats van fysieke tegels, niet te verwarren met de stenen. Laten we het gemarkeerde deel van de afbeelding hierboven gebruiken als onze patroontegel: twee hele stenen in de eerste rij en één geheel ingeklemd tussen twee halve stenen in de tweede rij. Let op hoe en waar de gaten zijn opgenomen, want die moeten worden opgenomen in de tegel met herhaald patroon.

Tijdens gebruik <pattern>, we moeten de patroon's definiëren width en height, die overeenkomen met de breedte en hoogte van de basistegel. Om de afmetingen te krijgen, hebben we een beetje wiskunde nodig:

Tile Width = 2(Brick Width) + 2(Gap) = 2(100) + 2(10) = 220
Tile Height = 2(Bright Height) + 2(Gap) = 2(30) + 2(10) = 80

Oké, dus onze patroontegel is 220✕80. We moeten ook de patternUnits attribuut, waarbij de waarde userSpaceOnUse betekent in wezen pixels. Tot slot, het toevoegen van een id naar het patroon is nodig zodat ernaar kan worden verwezen wanneer we er een ander element mee schilderen.

<pattern id="p" width="220" height="80" patternUnits="userSpaceOnUse"> <!-- pattern content here -->
</pattern>

Nu we de tegelafmetingen hebben vastgesteld, is de uitdaging om de code voor de tegel te maken op een manier die de afbeelding weergeeft met het kleinst mogelijke aantal bytes. Dit is waar we uiteindelijk mee hopen te eindigen:

De stenen (in zwart) en openingen (in wit) van het uiteindelijke lopende verbindingspatroon

Initiële opmaak (197 bytes)

De eenvoudigste en meest declaratieve benadering om dit patroon dat in me opkomt opnieuw te creëren, is door vijf rechthoeken te tekenen. Standaard is de fill van een SVG-element is zwart en de stroke transparant is. Dit werkt goed voor het optimaliseren van SVG-patronen, omdat we die niet expliciet in de code hoeven te declareren.

Elke regel in de onderstaande code definieert een rechthoek. De width en height zijn altijd ingesteld, en de x en y posities worden alleen ingesteld als een rechthoek is verschoven ten opzichte van de 0 positie.

<rect width="100" height="30"/>
<rect x="110" width="100" height="30"/>
<rect y="40" width="45" height="30"/>
<rect x="55" y="40" width="100" height="30"/>
<rect x="165" y="40" width="55" height="30"/>

De bovenste rij van de tegel bevatte twee stenen over de volledige breedte, de tweede steen is gepositioneerd om x="110" waardoor 10 pixels van de opening voor de steen. Op dezelfde manier is er 10 pixels van opening na, omdat de steen eindigt op 210 pixels (110 + 100 = 210) op de horizontale as, hoewel de <pattern> breedte is 220 pixels. We hebben net dat beetje extra ruimte nodig; anders zou de tweede steen versmelten met de eerste steen in de aangrenzende tegel.

De stenen in de tweede (onderste) rij zijn verschoven, zodat de rij twee halve stenen en een hele steen bevat. In dit geval willen we dat de stenen met halve breedte samensmelten, zodat er geen opening is aan het begin of het einde, zodat ze naadloos kunnen vloeien met de stenen in aangrenzende patroontegels. Bij het compenseren van deze stenen moeten we ook halve openingen opnemen, dus de x waarden zijn 55 en 165, Respectievelijk.

Element hergebruik, (-43B, 154B totaal)

Het lijkt inefficiënt om elke steen zo expliciet te definiëren. Is er geen manier om SVG-patronen te optimaliseren door in plaats daarvan de vormen opnieuw te gebruiken?

Ik denk niet dat het algemeen bekend is dat SVG een <use> element. Je kunt ermee naar een ander element verwijzen en dat element waarnaar wordt verwezen overal weergeven <use> is gebruikt. Dit bespaart nogal wat bytes omdat we de breedte en hoogte van elke steen kunnen weglaten, behalve de eerste.

Dat gezegd hebbende, <use> komt met een kleine prijs. Dat wil zeggen, we moeten een toevoegen id voor het element dat we willen hergebruiken.

<rect id="b" width="100" height="30"/>
<use href="#b" x="110"/>
<use href="#b" x="-55" y="40"/>
<use href="#b" x="55" y="40"/>
<use href="#b" x="165" y="40"/>

De kortste id mogelijk is één teken, dus ik koos "b" voor baksteen. De <use> element kan op dezelfde manier worden geplaatst als: <rect>Met x en y attributen als offsets. Omdat elke steen de volledige breedte heeft nu we zijn overgeschakeld naar <use> (vergeet niet dat we de stenen in de tweede rij van de patroontegel expliciet hebben gehalveerd), we moeten een negatief gebruiken x waarde in de tweede rij, zorg er dan voor dat de laatste steen uit de tegel overloopt voor die naadloze verbinding tussen stenen. Deze zijn echter oké, want alles wat buiten de patroontegel valt, wordt automatisch afgesneden.

Kun je enkele herhalende reeksen zien die efficiënter kunnen worden geschreven? Laten we aan die volgende werken.

Herschrijven naar pad (-54B, 100B totaal)

<path> is waarschijnlijk het krachtigste element in SVG. Je kunt zo ongeveer elke vorm tekenen met "commando's" in zijn d attribuut. Er zijn 20 commando's beschikbaar, maar we hebben alleen de eenvoudigste nodig voor rechthoeken.

Hier ben ik daarmee beland:

<path d="M0 0h100v30h-100z M110 0h100v30h-100 M0 40h45v30h-45z M55 40h100v30h-100z M165 40h55v30h-55z"/>

Ik weet het, super rare cijfers en letters! Ze hebben allemaal een betekenis, natuurlijk. Dit is wat er in dit specifieke geval gebeurt:

  • M{x} {y}: Verplaatst naar een punt op basis van coördinaten.
  • z: Sluit het huidige segment.
  • h{x}: Trekt een horizontale lijn vanaf het huidige punt, met de lengte van x in de richting gedefinieerd door het teken van x. kleine letters x geeft een relatieve coördinaat aan.
  • v{y}: Trekt een verticale lijn vanaf het huidige punt, met de lengte van y in de richting gedefinieerd door het teken van y. kleine letters y geeft een relatieve coördinaat aan.

Deze opmaak is veel beknopter dan de vorige (regelafbrekingen en inspringende witruimte is alleen voor de leesbaarheid). En hey, we zijn erin geslaagd om de helft van de oorspronkelijke grootte weg te snijden, tot 100 bytes. Toch geeft iets me het gevoel dat dit kleiner zou kunnen zijn...

Tegelrevisie (-38B, 62B totaal)

Heeft onze patroontegel geen herhalende delen? Het is duidelijk dat in de eerste rij een hele steen wordt herhaald, maar hoe zit het met de tweede rij? Het is wat moeilijker te zien, maar als we de middelste steen doormidden snijden wordt het duidelijk.

De linkerhelft voorafgaand aan de rode lijn is hetzelfde als de rechterkant.

Nou, de middelste steen is niet precies doormidden gesneden. Er is een kleine compensatie omdat we ook rekening moeten houden met het gat. Hoe dan ook, we hebben zojuist een eenvoudiger basistegelpatroon gevonden, wat minder bytes betekent! Dit betekent ook dat we de moeten halveren width onze <pattern> element van 220 tot 110.

<pattern id="p" width="110" height="80" patternUnits="userSpaceOnUse"> <!-- pattern content here -->
</pattern>

Laten we nu eens kijken hoe de vereenvoudigde tegel wordt getekend met <path>:

<path d="M0 0h100v30h-100z M0 40h45v30h-45z M55 40h55v30h-55z"/>

De grootte wordt teruggebracht tot 62 bytes, wat al minder is dan een derde van de oorspronkelijke grootte! Maar waarom hier stoppen als we nog meer kunnen doen!

Padopdrachten inkorten (-9B, 53B totaal)

Het is de moeite waard om wat dieper in te gaan op de <path> element omdat het meer hints biedt voor het optimaliseren van SVG-patronen. Een misvatting die ik heb gehad bij het werken met <path> gaat over hoe de fill attribuut werkt. Omdat ik in mijn jeugd veel met MS Paint heb gespeeld, heb ik geleerd dat elke vorm die ik met een effen kleur wil vullen, gesloten moet zijn, dwz geen open punten mag hebben. Anders zal de verf uit de vorm lekken en over alles heen lopen.

In SVG is dit echter niet waar. Laat me citeren de spec zelf:

De vulbewerking vult open subpaden door de vulbewerking uit te voeren alsof er een extra "pad sluiten" -opdracht aan het pad is toegevoegd om het laatste punt van het subpad te verbinden met het eerste punt van het subpad.

Dit betekent dat we de commando's voor het sluiten van het pad kunnen weglaten (z), omdat de subpaden automatisch als gesloten worden beschouwd wanneer ze zijn gevuld.

Een ander handig ding om te weten over padcommando's is dat ze in hoofdletters en kleine letters voorkomen. Kleine letters betekenen dat relatieve coördinaten worden gebruikt; hoofdletters betekenen dat in plaats daarvan absolute coördinaten worden gebruikt.

Het is een beetje lastiger dan dat met de H en V commando's omdat ze maar één coördinaat bevatten. Hier is hoe ik deze twee commando's zou beschrijven:

  • H{x}: Trekt een horizontale lijn vanaf het huidige punt naar de coördinaat x.
  • V{y}: Trekt een verticale lijn vanaf het huidige punt naar de coördinaat y.

Wanneer we de eerste steen in de patroontegel tekenen, beginnen we bij de (0,0) coördinaten. We trekken dan een horizontale lijn naar (100,0) en een verticale lijn naar (100,30), en trek ten slotte een horizontale lijn naar (0,30). We gebruikten de h-100 commando in de laatste regel, maar het is het equivalent van H0, wat twee bytes is in plaats van vijf. We kunnen twee vergelijkbare gebeurtenissen vervangen en de code van onze <path> tot dit:

<path d="M0 0h100v30H0 M0 40h45v30H0 M55 40h55v30H55"/>

Nog eens 9 bytes afgeschoren - hoeveel kleiner kunnen we gaan?

Overbrugging (-5B, 48B totaal)

De langste commando's die een volledig geoptimaliseerd SVG-patroon in de weg staan, zijn de "verplaats naar"-commando's die respectievelijk 4, 5 en 6 bytes in beslag nemen. Een beperking die we hebben is dat:

Een padgegevenssegment (indien aanwezig) moet beginnen met een "moveto" -opdracht.

Maar dat is oke. De eerste is sowieso de kortste. Als we de rijen verwisselen, kunnen we een paddefinitie bedenken waarbij we alleen horizontaal of verticaal tussen de stenen hoeven te bewegen. Wat als we de konden gebruiken h en v commando's daar in plaats van M?

Het pad begint bij de rode stip in de linkerbovenhoek. Rood zijn de padcommando's die worden ondersteund door pijlen, zwart zijn de coördinaten waar de pijlen naar wijzen.

Het bovenstaande diagram laat zien hoe de drie vormen kunnen worden getekend met een enkel pad. Merk op dat we gebruikmaken van het feit dat de fill operatie sluit automatisch het open gedeelte tussen (110,0) en (0,0). Met deze herschikking hebben we ook de opening naar links van de steen over de volledige breedte in de tweede rij verplaatst. Zo ziet de code eruit, nog steeds onderverdeeld in één steen per regel:

<path d="M0 0v30h50V0 h10v30h50 v10H10v30h100V0"/>

Zeker, we hebben de absoluut kleinste oplossing gevonden nu we nog maar 48 bytes hebben, toch?! We zullen…

Cijfers bijsnijden (-4B, 44B totaal)

Als je een beetje flexibel kunt zijn met de afmetingen, is er nog een kleine manier waarop we SVG-patronen kunnen optimaliseren. We hebben gewerkt met een steenbreedte van 100 pixels, maar dat is drie bytes. Het veranderen in 90 betekent een byte minder wanneer we het moeten schrijven. Evenzo gebruikten we een opening van 10 pixels — maar als we het veranderen in 8 in plaats daarvan slaan we een byte op voor elk van die gebeurtenissen.

<path d="M0 0v30h45V0 h8v30h45 v8H8v30h90V0"/>

Dit betekent natuurlijk ook dat we de patroonafmetingen daarop moeten aanpassen. Hier is de laatste geoptimaliseerde SVG-patrooncode:

<pattern id="p" width="98" height="76" patternUnits="userSpaceOnUse"> <path d="M0 0v30h45V0h8v30h45v8H8v30h90V0"/>
</pattern>

De tweede regel in het bovenstaande fragment - de inspringingen niet meegerekend - is 44 bytes. We kwamen hier van 197 bytes in zes iteraties. Dat is een dikke 77.7% verkleining!

Ik vraag me echter af ... is dit echt de kleinst mogelijke maat? Hebben we gekeken naar alle mogelijke manieren om SVG-patronen te optimaliseren?

Ik nodig je uit om te proberen deze code verder te verkleinen, of zelfs te experimenteren met alternatieve methoden om SVG-patronen te optimaliseren. Ik zou graag zien of we het echte wereldwijde minimum kunnen vinden met de wijsheid van de menigte!

Meer over het maken en optimaliseren van SVG-patronen

Als je meer wilt weten over het maken en optimaliseren van SVG-patronen, lees dan mijn artikel over patronen maken met SVG-filters. Of, als u een galerij met meer dan 60 patronen wilt bekijken, kunt u de PetitePatterns CodePen-collectie. Eindelijk, je bent van harte welkom om te kijken mijn tutorials op YouTube om je te helpen nog dieper in SVG-patronen te komen.


SVG-patronen optimaliseren tot hun kleinste formaat oorspronkelijk gepubliceerd op CSS-trucs. Je zou moeten ontvang de nieuwsbrief.

spot_img

Laatste intelligentie

spot_img