Eenvoudig Cirkeldiagram met CSS Variabelen en de Houdini ‘ Magic

0
680

Ik kreeg het idee voor het doen van iets van de soort als ik struikelde over deze interactieve SVG-cirkeldiagram. Terwijl de SVG-code is net zo compact is als het wordt (één <cirkel> element!), met behulp van slagen voor het maken van een cirkeldiagram segmenten is problematisch als we in de problemen met de weergave in Windows voor Firefox en Rand. Plus, in 2018, kunnen we een hoop bereiken meer met een veel minder JavaScript!

AI kwam het volgende resultaat naar een enkele HTML-element voor de grafiek en zeer kleine JavaScript. De toekomst moet volledig elimineren van de noodzaak van JavaScript, maar daarover later meer.

De laatste cirkeldiagram resultaat.

Sommige van jullie herinneren zich misschien Lea Verou Ontbrekende Segment praten—mijn oplossing is gebaseerd op techniek. Dit artikel analyseert hoe het allemaal werkt, laten zien wat we kunnen doen in termen van graceful degradation en andere manieren om deze techniek kan worden ingezet.

De HTML

Wij maken gebruik van Pug voor het genereren van de HTML-code van een data-object bevat eenheidloze percentage waarden voor de afgelopen drie jaar:

– var data = { 2016: 20, 2017: 26, 2018: 29 }

Wij maken al onze elementen bevinden zich in een .wikkel element. Dan, wij herhalen via onze data object en, voor elk van de eigenschappen, maken we een radio-ingang met een bijbehorende label. Na deze, voegen we een .taart element toe aan de mix.

– var darr = [], val;

.wrap
– voor(var p in de gegevensverwerking) {
– als(!val) val = data[p];
ingang(id=`o${p}` name=’o’ type=’radio’ checked=val == data[p])
label for=`o${p}`) #{p}
– darr.push (`${[p]}% voor het jaar ${p}`)
– }
.taart(aria-label=`Waarde als cirkeldiagram. ${darr.join(‘, ‘)}.`
rol=’graphics-document group’)

Merk op dat we ook zeker dat alleen de eerste radio-ingang is ingeschakeld.

Het passeren van de aangepaste eigenschappen van het deelvenster CSS

Ik normaal gesproken niet, zoals het zetten van stijlen in HTML, maar in dit specifieke geval, het is een zeer nuttige manier om aangepaste waarden van de eigenschap om de CSS en ervoor te zorgen dat we alleen moeten bijwerken dingen in een plaats als we het nodig om te veranderen van onze data punten—de Pug-code. De CSS blijft hetzelfde.

De truc is om een eenheidloze percentage –p op .taart element voor elke radio-input die kan worden gecontroleerd:

stijl
– voor(var p in de gegevensverwerking) {
| #o#{p}:gecontroleerd ~ .taart { –p: #{[p]} }
– }

We gebruiken dit percentage voor een kegelsnede-gradient() op .taart element na het maken van dat geen van de dimensies (inclusief border en padding (opvulling) 0:

$d: 20rem;

.wrap { width: $d; }

.taart {
vulling: 50%;
achtergrond: conic-gradient(#ab3e5b calc(var (- p)*1%), #ef746f 0%);
}

Merk op dat dit vereist native conic-gradient() ondersteuning sinds de polyfill niet werkt met CSS variabelen. Op het moment dat deze grenzen ondersteuning te Knipperen browsers met de Experimentele Web-Platform functies vlag ingeschakeld, maar dingen zijn gebonden om beter te worden.

De Experimentele Web-Platform functies vlag ingeschakeld in Chrome.

We hebben nu een werkende skelet van onze demo—opname van een ander jaar via de radio knoppen resulteert in een andere kegelvormige-gradient()!

De basisfunctionaliteit we zijn na (live demo, Blink browsers met vlag alleen ingeschakeld).

Het weergeven van de waarde

De volgende stap is om daadwerkelijk de weergave van de actuele waarde en we doen dit via een pseudo-element. Helaas, het aantal-waarde CSS variabelen kan niet worden gebruikt voor de waarde van de inhoud eigendom, zodat we dit met behulp van de teller() hack.

.pie:after {
teller-reset: p var (- p);
inhoud: teller(p) ‘%’;
}

We hebben ook veranderd de kleur en de font-grootte-eigenschappen, zodat onze pseudo-element is een beetje meer zichtbaar:

Het weergeven van de waarde op de kaart (live demo, Blink browsers met vlag alleen ingeschakeld).

Smoothing dingen uit

We willen geen abrupte veranderingen tussen waarden, zodat we vlot met de hulp van een CSS-overgang. Voordat we kunnen overgaan of animeren –p variabele, die we nodig hebben om te registreren in JavaScript:

CSS.registerProperty({
naam: de ‘- p’,
syntaxis: ‘<integer>’,
initialValue: 0,
erft: true
});

Merk op dat het gebruik van <aantal> in plaats van <integer> oorzaken van de weergegeven waarde naar 0 tijdens de overgang als onze teller moet een geheel getal zijn. Met dank aan Lea Verou voor het helpen me hier uit!

Ook de opmerking dat uitdrukkelijk de erft is verplicht. Dit was niet het geval tot voor kort.

Dit is de JavaScript die we nodig hebben voor deze demo-en, in de toekomst, we moeten het niet eens nodig dat dit veel als we kunnen om te registreren aangepaste eigenschappen van de CSS.

Met dat uit de weg, we kunnen het toevoegen van een overgang op ons .taart element.

.taart {
/* stijlen als voorheen */
overgang: – p .5s;
}

En dat is het voor de functionaliteit! Alles gebeurt met één element, een aangepaste variabele, en een strooi van Houdini magie!

Interactieve cirkeldiagram (live demo, Blink browsers met vlag alleen ingeschakeld).

Prettifying raakt

Tijdens onze demo is functioneel, het ziet er allesbehalve vrij op dit punt. Dus, laten we zorgen dat, terwijl we toch bezig zijn!

Het maken van de taart… een taart!

Aangezien de aanwezigheid van :na verhoogt de hoogte van haar .taart ouder, hebben we absoluut positioneren. En omdat we willen ons .taart element om te kijken meer als een echte taart, wij maken het rond met border-radius: 50%.

De afronding van onze taart (live demo, Blink browsers met vlag alleen ingeschakeld).

We willen ook weer te geven het percentage dat in het midden van de donkere cirkelsegment.

Om dit te doen, moeten we eerst de positie dood in het midden van de .taart element. Standaard :after pseudo-element wordt weergegeven nadat de ouders op de inhoud. Sinds .pie heeft geen inhoud in dit geval, in de linkerbovenhoek van de :after pseudo-element is in de linkerbovenhoek van de ouder-content-box. Hier, de content-box is een 0x0 doos in het midden van de padding-box. Vergeet niet dat we ons gesteld hebben op de vulling .taart 50%—een waarde die in vergelijking met de wrapper breedte voor zowel de horizontale als de verticale richting!

Dit betekent dat de linkerbovenhoek van :na in het midden van haar ouders, dus een te vertalen(-50%, -50%) op het verschuift naar links door de helft van zijn eigen breedte en door de helft van zijn eigen hoogte, waardoor het zijn eigen middelpunt samenvalt met dat van de .pie.

Vergeet niet dat de %-waarde vertalingen zijn in vergelijking met de afmetingen van het element dat ze zijn toegepast op langs de desbetreffende as. In andere woorden, een %-waarde vertaling langs de x-as is in vergelijking met het element breedte, een %-waarde vertaling langs de y-as is in vergelijking met de hoogte en een %-waarde vertaling langs de z-as is in verhouding tot de diepte, die is altijd 0, want alle elementen zijn platte, tweedimensionale vakjes met 0 diepte langs de derde as.

De positionering van de waarde label in het midden van de taart (live demo, Blink browsers met vlag alleen ingeschakeld).

Vervolgens draaien we de waarde die de positieve helft van de x-as splitst de donkere plakje in twee gelijke helften en dan het vertalen van het door een halve cirkel straal langs deze nu geroteerd x-as.

Zie de Pen door thebabydino (@thebabydino) op CodePen.

Wat we nodig hebben om erachter te komen hoe veel om te draaien :after pseudo-element, zodat de x-as splitst de donkere plakje in twee gelijke helften. Laten we breken die naar beneden!

In eerste instantie, de x-as is de horizontale, in de richting van de rechter, dus om het in de gewenste richting, moeten we eerst om te draaien zodat deze wijst naar boven en gaan langs de beginnend rand van het schijfje. Dan moet roteren met de klok mee door de helft van een plakje.

Om de as te wijzen, moeten we om te draaien door -90deg. Het minteken is te wijten aan het feit dat positieve waarden volgen een richting van de klok en we gaan de andere kant op.

Zie de Pen door thebabydino (@thebabydino) op CodePen.

We moeten om het te draaien door de helft van een plakje.

Zie de Pen door thebabydino (@thebabydino) op CodePen.

Maar hoeveel is de helft van een plakje?

Goed, weten we al welk percentage van de taart dit deel vertegenwoordigt: het is onze aangepaste eigenschap, –blz. Als we verdelen die waarde door 100 en vermenigvuldigen met 360deg (of 1turn, het maakt niet uit welke eenheid gebruikt wordt), dan krijgen we het centrum van hoek van onze donkere plakje.

Na de -90deg rotatie, moeten we om te draaien :na door de helft van deze centrale hoek in de richting van de klok (positieve) richting.

Dit betekent dat we de volgende transformatie-keten:

vertalen(-50%, -50%) draaien(calc(.5*var (- p)/100*1turn – 90deg)) vertalen(.25*$d);

De laatste vertaling is door een kwart van $d, dat is de wrapper breedte en geeft ons het .cirkel diameter. (Sinds de inhoud doos .pie is een 0x0 vak, het heeft geen grens en beide links en rechts opvullen 50% van de wrapper ouder breedte.) Het .taart straal is de helft van de diameter, wat betekent dat de helft van de straal van een kwart van de diameter ($d).

Nu de waarde label is gepositioneerd waar we willen zijn:

De positionering van de waarde label in het midden van het segment (live demo, Blink browsers met vlag alleen ingeschakeld).

Echter, er is nog één probleem: we willen het niet gedraaid worden, want dat kan er echt onhandig en nek te buigen in bepaalde hoeken. Om dit op te lossen, keren we terug de rotatie aan het einde. Om dingen makkelijker te maken voor onszelf, we slaan de rotatiehoek in een CSS-variabele die we noemen –een:

–a: calc(.5*var (- p)/100*1turn – 90deg);
transformeren:
vertalen(-50%, -50%)
draaien(var (–))
vertalen(.25*$d)
draaien(calc(-1*var (–)));

Veel beter!

De positionering van de waarde label in het midden van het segment, nu horizontaal (live demo, Blink browsers met vlag alleen ingeschakeld).

Indeling

We willen het geheel in het midden van het scherm, zodat we dit oplossen met een keurige raster truc:

body {
display: grid;
plaats-items: center center;
margin: 0;
min-height: 100vh
}

Oke, dit zet de gehele .wikkel element in het midden:

De positionering van het geheel in het midden (live demo, Blink browsers met vlag alleen ingeschakeld).

De volgende stap is om de bovenstaande taartdiagram van de keuzerondjes. Dit doen We met een flexbox lay-out op .wikkel element:

.wrap {
display: flex;
flex-wrap: wrap-achteruit;
rechtvaardiging-inhoud: center;
breedte: $d;
}

Het plaatsen van het taartdiagram boven de radio knoppen (live demo, Blink browsers met vlag alleen ingeschakeld).

Styling van de radio knoppen

…of beter gezegd, we styling van de radio-knop labels, omdat het eerste ding dat we doen is het verbergen van de radio-ingangen:

[type=’radio’] {
position: absolute;
links: -100vw;
}

Na het verbergen van de radio buttons (live demo, Blink browsers met vlag alleen ingeschakeld).

Sinds dit laat ons achter met een aantal zeer lelijk labels die zijn zeer moeilijk te onderscheiden van elkaar, laten we elk een bepaalde marge) en padding (opvulling, zodat ze niet kijken dus samen gepropt, plus achtergronden, zodat hun aanklikbare gebieden zijn duidelijk gemarkeerd. We kunnen zelfs zeggen vak en tekst schaduwen voor sommige 3D-effecten. En natuurlijk, wij kunnen het maken van een apart geval voor wanneer de overeenkomstige ingangen zijn :aangevinkt.

$c: #ecf081 #b3cc57;

[type=’radio’] {
/* hetzelfde als voorheen */

+ – label {
marge: 3em .5em .5em;
padding: .25em .5em;
border-radius: 5px;
box-shadow: 1px 1px ne($c, 2);
achtergrond: ne($c, 1);
font-size: 1.25 em;
text-shadow: 1px 1px #fff;
cursor: pointer;
}

en:gecontroleerd {
+ – label {
box-shadow: inset -1px -1px ne($c, 1);
achtergrond: ne($c, 2);
color: #fff;
text-shadow: 1px 1px #000;
}
}
}

We hebben ook opgeblazen de font-grootte een beetje en het instellen van een border-radius te glad uit de hoeken:

Na de styling van de radio-knop labels (live demo, Blink browsers met vlag alleen ingeschakeld).

Laatste prettifying raakt

Laten we een achtergrond op het lichaam, aanpassen van het lettertype van het hele ding, en voeg een overgang voor de radio labels:

De laatste cirkeldiagram resultaat (live demo, Blink browsers met vlag alleen ingeschakeld).

Graceful degradation

Tijdens onze demo ziet er nu goed in te Knipperen browsers met de vlag ingeschakeld, het ziet er verschrikkelijk in alle andere browsers…en dat is de meeste browsers!

Ten eerste, laten we ons werk in een @ondersteunt blok gecontroleerd voor de inheemse conic-gradient() te ondersteunen, zodat de browsers die het ondersteunen zal maken van het cirkeldiagram. Dit omvat onze conic-gradient(), de vulling geeft de taart gelijke horizontale en verticale afmetingen, de border-radius, dat maakt de cirkel rond, en de transformatie keten die posities de waarde label in het midden van het cirkelsegment.

.taart {
@ondersteunt (achtergrond: conic-gradient(tan, rood)) {
vulling: 50%;
border-radius: 50%;
achtergrond: conic-gradient(var(–stop-lijst));
–a: calc(.5*var (- p)/100*1turn – 90deg);
–pos: draaien(var (–))
vertalen(#{.25*$d})
draaien(calc(-1*var (–)));
}
}
}

Nu, laten we de bouw van een staafdiagram als een vangnet voor alle andere browsers met behulp van linear-gradient(). We willen onze bar te strekken over de .wikkel element, zodat de horizontale vulling is nog steeds 50%, maar de verticaal als in een smalle balk. We hebben nog steeds de grafiek wilt worden groot genoeg om te passen op de waarde van het label. Dit betekent dat wij zullen gaan met kleinere verticale vulling. We zullen ook een verlaging van de border-radius, aangezien 50% zou ons een ellips en wat we nodig hebben is een rechthoek met afgeronde hoeken.

De terugval zal ook vervangen conic-gradient() met een links-naar-rechts-linear-gradient(). Omdat zowel de linear-gradient() maken van de fallback-staafdiagram en de kegelvormige-gradient() maken van het cirkeldiagram maken gebruik van dezelfde lijst stop, we kunnen dit opgeslagen in een CSS-variabele (–stop-lijst) zodat we het zelfs niet hebben herhaald in de gecompileerde CSS.

Tot slot willen we de waarde label in het midden van de bar voor de fallback aangezien we niet hebben cirkelsegmenten meer. Dit betekent dat we alle post-centreren positionering in een CSS-variabele (–pos) waarvan de waarde wordt niets in de no-conisch-gradient() ondersteuning van het geval en de transformatie ketting:

.taart {
padding: 1.5 em 50%;
border-radius: 5px;
–stop-lijst: #ab3e5b calc(var (- p)*1%), #ef746f 0%;
background: linear-gradient(90deg, var(–stop-lijst));
/* hetzelfde als voorheen */

en:after {
transformeren: translate(-50%, -50%) var(–pos, #{unquote(‘ ‘)});
/* hetzelfde als voorheen */
}

@ondersteunt (achtergrond: conic-gradient(tan, rood)) {
vulling: 50%;
border-radius: 50%;
achtergrond: conic-gradient(var(–stop-lijst));
–a: calc(.5*var (- p)/100*1turn – 90deg);
–pos: draaien(var (–))
vertalen(#{.25*$d})
draaien(calc(-1*var (–)));
}
}
}

We hebben ook overschakelen naar het gebruik van een flexbox lay-out op het lichaam (omdat, zo slim als het kan, het raster is messed up in de Rand).

body {
display: flex;
lijn-items: center;
rechtvaardiging-inhoud: center;
/* hetzelfde als voorheen */
}

Dit alles geeft ons een staafdiagram fallback voor de browsers die het niet ondersteunen van conic-gradient().

De fallback voor de laatste cirkeldiagram resultaat (live demo).

Responsifying het allemaal

Één van de problemen die we nog hebben, is dat, als de viewport is smaller dan de cirkel diameter, de dingen niet zo goed meer.

CSS variabelen en media queries om de redding!

We stellen de diameter van een CSS-variabele (–d) die wordt gebruikt voor het instellen van de taart afmetingen en de positie van de waarde label in het midden van ons segment.

.wrap {
–d: #{$d};
breedte: var(–d);
/* hetzelfde als voorheen */

@media (max-width: $d) { –d: 95vw }
}

Onder bepaalde viewport breedte, hebben we ook een verlaging van de font-grootte van de marge voor onze <label> – elementen, en wij zijn niet van de positie van de waarde label in het midden van de donkere cirkelsegment meer, maar eerder in het midden van de taart zelf:

.wrap {
/* hetzelfde als voorheen */

@media (max-width: 265px) { font-size: .75em; }
}

[type=’radio’] {
/* hetzelfde als voorheen */

+ – label {
/* hetzelfde als voorheen */

@media (max-width: 195px) { margin-top: .25em; }
}
}

.taart{
/* hetzelfde als voorheen */

@media (max-width: 160px) { –pos: #{unquote(‘ ‘)} }
}

Dit geeft ons onze uiteindelijke resultaat: een responsive cirkeldiagram in browsers ondersteunen van conic-gradient() zelf. En, ook al is dat helaas slechts Knipperen browsers met de Experimentele Web-Platform functies vlag ingeschakeld voor nu, we hebben een stevige terugval waardoor een responsive staafdiagram voor alle andere browsers. We hebben ook animeren tussen waarden en, nogmaals, dat is gewoon Knipperen browsers met de Experimentele Web-Platform functies vlag ingeschakeld op dit punt.

Zie de Pen door thebabydino (@thebabydino) op CodePen.

Bonus: radiale vooruitgang!

Ook kunnen We toepassen van dit concept voor het bouwen van een radiale voortgang indicator zoals hieronder (geïnspireerd door deze Pen):

De radiale vooruitgang en de fallback.

De techniek is vrij veel het zelfde, behalve verlaten we de waarde label dood in het midden en zet de kegelvormige-gradient() op :voordat pseudo-element. Dit komt omdat we gebruik maken van een masker om zich te ontdoen van alles, behalve voor een dunne buitenste ring en, als we het kegelvormige-gradient() en het masker op het element zelf, vervolgens het masker zou het verbergen van de waarde label aan de binnenkant en dat willen we zichtbaar.

Door te klikken op de knop<>, een nieuwe waarde voor onze eenheidloze percentage (–p) wordt willekeurig gegenereerd en wij overgang soepel tussen de waarden. Het instellen van een vaste transitie-duur zou maken van een echt langzame overgang tussen twee dicht waarden (bijv. 47% 49%) en een zeer snelle overgang bij het verplaatsen tussen de waarden met een grotere kloof tussen (bijvoorbeeld 3% tot 98%). We krijgen rond dit door het maken van de overgang-de duur is afhankelijk van de absolute waarde van het verschil tussen de vorige waarde van –p en de nieuw gegenereerde waarde.

[id=’uit’] { /* radiale vooruitgang element */
overgang: – p calc(var(–dp, 1)*1s) ease-out;
}
const _GEN = document.getElementById(‘gen’),
_OUT = document.getElementById(‘out’);

_GEN.addEventListener(‘klik’, e => {
laat old_perc = ~~_OUT.stijl.getPropertyValue (‘- p’),
new_perc = Wiskunde.round(100*Math.random());

_OUT.stijl.setProperty (‘- p’, new_perc);
_OUT.stijl.setProperty(‘–dp’, .01*Math.abs(old_perc – new_perc));
_OUT.setAttribute(‘aria-label’, `Grafische weergave van de gegenereerde percentage: ${new_perc}% van 100%.`)
}, false);

Dit geeft ons een mooi geanimeerde radiale voortgang indicator voor browsers ondersteunen alle nieuwe en glanzend functies. We hebben een lineaire fallback voor andere browsers:

Zie de Pen door thebabydino (@thebabydino) op CodePen.