Clip-path: creare maschere con CSS e SVG
Il clipping (o ritaglio) è l'operazione che permette di visualizzare una precisa porzione di un elemento di una pagina web, nascondendo la parte esterna all'area individuata. Può essere effettuata su qualsiasi elemento HTML, e su qualsiasi elemento grafico o container SVG. Le caratteristiche principali sono:
- il clipping path, ovvero la linea che separa le due aree: quella visibile e quella trasparente (mascherata).
- la clipping region è l'area visibile, quella all'interno del clipping path.
La definizione di un'area di clipping modifica solo il modo in cui l'elemento viene reso a video, ma non modifica la sua struttura all'interno del DOM. Il container, infatti, seguirà il flusso all'interno del documento, mantenendo inalterate posizione, forma e dimensioni.
La proprietà clip
Una prima semplice tecnica di ritaglio era già offerta da CSS 2.1, con la proprietà clip
. Questa supporta un unico valore, espresso dalla funzione rect
, e va utilizzata solo su elementi con posizionamento assoluto. È strutturata come segue:
clip: rect(<top>, <right>, <bottom>, <left>);
Gli argomenti della funzione rect corrispondono alle distanze della regione di clipping dai bordi dell'elemento. Nell'esempio che segue, applichiamo la proprietà clip ad un'immagine di 1024px:
<style type="text/css">
.img1
{
height: 336px;
width: 1024px;
position: absolute;
clip: rect(40px, 600px, 290px, 10px);
}
</style>
<img class="img1" src="images/nisyros.jpg" />
La superficie visibile sarà, dunque, ridotta rispetto alle dimensioni dell'immagine originale.
La proprietà clip
, nella sua semplicità, permette di creare solo regioni di ritaglio rettangolari. Con CSS3 viene introdotta la proprietà clip-path
, che permette un controllo più preciso della forma delle aree ritagliate (clipping path).
La proprietà clip-path
La proprietà clip-path
è definita nell'ambito del CSS Masking Module Level 1. Il valore che questa può assumere potrà essere il riferimento ad un elemento SVG <clipPath>
, o una delle quattro forme base CSS (CSS shapes).
La soluzione delle forme base non richiede il ricorso ad elementi SVG, ma la semplice definizione di un tracciato definito dalle funzioni circle()
, ellipse()
, inset()
e polygon()
, come nell'esempio che segue:
<style type="text/css">
.img1{
height: 336px;
width: 1024px;
-webkit-clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
}
</style>
<img class="img1" src="images/nisiros.jpg" />
L'immagine mostra l'output generato dalla proprietà clip-path e dalla funzione polygon in Safari 7.0.3
Al momento in cui scriviamo, nessuno dei maggiori browser fornisce supporto completo alle CSS shapes, sebbene la nuova funzionalità sia già parzialmente supportata da Chrome e Safari.
L'elemento <clipPath>
In alternativa alle forme base, il valore di clip-path
può essere costituito da un riferimento ad un elemento SVG <clipPath>
. A differenza della soluzione che adotta il puro CSS, questa seconda soluzione permette di dare al ritaglio una forma assolutamente arbitraria. Allo scopo, si può ricorrere ad una forma base SVG, come <rect>
, <circle>
, <ellipse>
, <polygon>
, oppure ricorrere ad uno o più elementi grafici <path>
, <image>
, <text>
. Rinviamo alla documentazione online per la descrizione di ognuno di questi elementi, limitandoci qui a presentare alcuni esempi pratici.
Se le forme da creare sono semplici, basterà calcolare i vertici e le dimensioni richieste. Supponiamo, come primo esempio, di voler creare una div
che scorra all'interno di una regione di clipping, come mostrato nell'immagine.
L'immagine mostra una semplice area di clipping ottenuta con una forma base polygon
La div
verde scorre all'interno di un contenitore cui viene assegnata la proprietà clip-path
. Quello che segue è il markup dei due blocchi:
<div id="container">
<div class="content">
<h2>Title</h2>
<h3>Sub title</h3>
<p>Paragraph</p>
<ol>
<li>List item</li>
<li>List item</li>
<li>List item</li>
<li>List item</li>
<li>List item</li>
</ol>
<p>Lorem ipsum dolor ...</p>
<p class="center">
<img src="images/HTML5_Logo.png">
</p>
</div>
</div>
Si passa poi al layer della presentazione:
<style type="text/css">
#container
{
height: 360px; width: 360px;
overflow-y: scroll; overflow-x: hidden;
-webkit-clip-path: polygon(0px 0px, 20px 20px, 40px 0px, 60px 20px, 80px 0px, 100px 20px, 120px 0px, 140px 20px, 160px 0px, 180px 20px, 200px 0px, 220px 20px, 240px 0px, 260px 20px, 280px 0px, 300px 20px, 320px 0px, 340px 20px, 360px 0px, 360px 360px, 0px 360px);
clip-path: url(#svgPath);
background-color: #0cc;
margin: 10px;
border: 0;
}
.content
{
min-height: 600px;
padding: 20px;
}
.center{ text-align: center; }
</style>
All'elemento #container
viene assegnata due volte la proprietà clip-path
, come prevede la specifica e con il prefisso -webkit-
. Ciò in quanto, al momento in cui si scrive, Webkit non riconosce la forma base SVG <polygon>
come area di ritaglio. Per la corretta resa a video in Chrome e Safari, quindi, si farà ricorso alla forma base CSS.
Per Firefox, al contrario, si farà riferimento ad un elemento <clipPath>
, definito come segue:
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<polygon
points="0 0, 20 20, 40 0, 60 20, 80 0, 100 20, 120 0, 140 20, 160 0, 180 20, 200 0, 220 20, 240 0, 260 20, 280 0, 300 20, 320 0, 340 20, 360 0, 360 360, 0 360" />
</clipPath>
</defs>
</svg>
Se le forme sono complesse, potrebbe essere opportuno ricorrere ad un programma di grafica vettoriale come Inkscape.
Supponiamo di voler visualizzare un ritaglio a forma di stella di un'immagine marina. Per prima cosa, inseriamo l'immagine nel documento HTML:
<img class="img1" src="images/nisiros.jpg" />
<style type="text/css">
.img1{
height: 336px;
width: 1024px;
-webkit-clip-path: url(#svgPath);
clip-path: url(#svgPath);
}
</style>
Come si noterà subito, in questo caso si assegna lo stesso valore di clip-path
anche alla dichiarazione destinata a Webkit. Ciò in quanto il motore di rendering di Chrome e Safari accetta l'elemento <path>
come area di clipping.
A questo punto si creerà la forma in Inkscape (o altro programma di grafica vettoriale).
Una stella creata con Inkscape
Una volta creata, la forma va esportata in formato SVG (preferibilmente SVG puro). Aperto il file con un editor di testo, si copia il markup dell'elemento <path>
e lo si incolla nel documento HTML:
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<path
d="M 328.57143,423.79076 214.92963,412.17017 140.9316,499.19769 116.86618,387.52695 11.231446,344.04362 110,286.6479 118.71218,172.7462 203.81992,248.94443 314.83908,222.03264 268.67,326.52146 z"
transform="translate(400,-170)"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
</clipPath>
</defs>
</svg>
La proprietà transform
permette di collocare con precisione la clipping region al di sopra dell'isola di Nisyros:
Ecco come appare il clip-path applicato all'immagine
È possibile utilizzare più forme contemporaneamente, per ottenere effetti grafici composti. Proviamo, ad esempio, ad utilizzare più elementi <circle>
:
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<circle cx="100" cy="140" r="80" />
<circle cx="180" cy="60" r="30" />
<circle cx="280" cy="130" r="90" />
<circle cx="420" cy="220" r="60" />
<circle cx="600" cy="180" r="150" />
<circle cx="880" cy="120" r="100" />
</clipPath>
</defs>
</svg>
L'immagine mostra come appare adesso l'isola greca.
Per un'anomalia segnalata in questo articolo, non saranno visualizzati come regioni di ritaglio le forme raggruppate in un elemento <g>
.
La specifica colloca l'elemento <text> tra gli elementi grafici SVG. Come tale, questo può essere utilizzato per generare un'area di clipping su un qualsiasi elemento visualizzato come blocco. La logica è esattamente la stessa degli esempi precedenti: viene inserita un'immagine:
<img class="img1" src="images/nisyros.jpg" />
Viene, poi, definito lo stile dell'immagine:
<style type="text/css">
.img1{
min-height: 336px;
width: 1024px;
-webkit-clip-path: url(#svgPath);
clip-path: url(#svgPath);
}
</style>
Infine, all'interno dell'elemento SVG <clipPath>
, si aggiunge l'elemento <text>
:
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<text
x="100"
y="240"
textLength="824"
lengthAdjust="spacing"
font-family="Helvetica"
font-size="240px"
font-weight="bold">
Nisyros
</text>
</clipPath>
</defs>
</svg>
La regione di clipping può essere definita anche da un testo SVG
Gli attributi x
e y
dell'elemento <text>
stabiliscono la distanza orizzontale e verticale dai margini del contenitore (la distanza stabilita da y
si riferisce alla baseline del testo). L'attributo textLength
imposta la lunghezza in pixel del testo, mentre lengthAdjust
stabilisce quale parametro debba essere adattato al valore di textLength
(in questo caso è la spaziatura tra i caratteri).
Rinviamo alla specifica per una descrizione dettagliata dell'elemento <text>
.
L'animazione del clip-path con CSS
Nel nostro primo esempio, abbiamo assegnato alla proprietà clip-path
la funzione polygon()
per generare un'area di ritaglio irregolare. Riprendiamo il codice CSS:
.img1{
height: 336px;
width: 1024px;
-webkit-clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
}
Abbiamo testato questo codice su diversi browser e verificato che al momento le forme base CSS sono supportate solo da Chrome e Safari.
Una volta creata la forma, abbiamo provato a modificarla con una semplice animazione con la proprietà transition
. Il nostro codice CSS cambia così:
.img1{
height: 336px;
width: 1024px;
-webkit-clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
clip-path: polygon(10px 10px, 900px 20px, 800px 300px, 50px 260px);
-webkit-transition: all .5s ease-in-out;
transition: all .5s ease-in-out;
}
.img1:hover{
-webkit-clip-path: polygon(0px 0px, 1024px 0px, 1024px 336px, 0px 336px);
clip-path: polygon(0px 0px, 1024px 0px, 1024px 336px, 0px 336px);
}
La proprietà transition
è lo short-hand delle proprietà transition-property
, transition-duration
, transition-timing-function
e transition-delay
. In questo esempio, la transizione viene applicata a tutte le proprietà che cambiano dallo stato normale allo stato :hover
; ha una durata di mezzo secondo; segue l'andamento della funzione ease-in-out
; non specifichiamo nessun ritardo nell'avvio della transizione. L'effetto è semplice: al passaggio del mouse sull'immagine, il ritaglio cambia forma, passando gradatamente dalla figura irregolare alla fotografia intera.
L'esempio dal vivo (al momento la funzione CSS polygon
è supportata solo da Chrome e Safari)
Gli eventi del mouse
La versione developers della specifica afferma che un valore della proprietà clip-path
diverso da none
genera uno stacking context (ovvero un layer che va a collocarsi al di sopra dell'elemento ritagliato), esattamente come avviene per valori diversi da 1 della proprietà CSS opacity.
Altra importante previsione della specifica è che gli eventi del mouse non devono essere eseguiti sull'aera esterna al ritaglio (si veda il documento online).
Nei nostri test i browser reagiscono in modo esattamente opposto rispetto alla previsione: Firefox 31 esegue l'evento solo sull'area di ritaglio; Chrome 36 e Safari 7 eseguono l'evento sia sull'area di clipping che sull'area esterna.
Torniamo all'esempio della stella e modifichiamo lo stile come segue:
.img1
{
min-height: 336px;
width: 1024px;
-webkit-clip-path: url(#svgPath);
clip-path: url(#svgPath);
}
.img1:hover
{
opacity: .5;
}
Il markup sarà sempre lo stesso:
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<path
d="M 328.57143,423.79076 214.92963,412.17017 140.9316,499.19769 116.86618,387.52695 11.231446,344.04362 110,286.6479 118.71218,172.7462 203.81992,248.94443 314.83908,222.03264 268.67,326.52146 z"
transform="translate(400,-170)"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
</clipPath>
</defs>
</svg>
<img class="img1" src="images/nisyros.jpg" />
Passando il cursore del mouse, l'area di ritaglio diventerà semitrasparente, con le differenze già illustrate tra i diversi browser.
L'immagine mostra l'effetto hover in Firefox
Abbiamo anche provato a creare una transizione al passaggio del cursore. Dai test è emerso che Firefox esegue correttamente l'animazione, al contrario di Webkit, che genera un'anomalia che al momento non sembra risolvibile.
Nella seconda parte dell'articolo vedremo come ottenere maschere animate e quali siano i vincoli sui browser
Animazioni in SVG
Il ricorso al CSS puro per ritagliare una parte di un elemento è una soluzione estremamente semplice ed è una soluzione ideale per creare animazioni base. Qualora, però, gli obiettivi di sviluppo lo richiedano, possono diventare necessarie soluzioni più avanzate, come l'animazione SVG con SMIL, che consente di animare gli attributi numerici, oltre che gli attributi traslation
e rotation
di un elemento. Consente, inoltre, di modificarne i colori e generare un movimento lungo un tracciato.
Tutto ciò può essere ottenuto grazie agli elementi <animate>
, <animateTransform>
, <animateMotion>
, <animateColor>
, <set>
(rinviamo alla specifica per i dettagli).
Supponiamo, quindi, di avere una regione di clipping individuata da un elemento <circle>
e che vogliamo ingrandire la regione fino a visualizzare l'intero elemento img
(le dimensioni dell'immagine sono di 1024 x 336 pixel).
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<circle cx="512" cy="168" r="64">
<animate
attributeName="r"
attributeType="XML"
from="64"
to="1400"
begin="2s"
dur="6s"
fill="freeze"
repeatCount="1" />
</circle>
</clipPath>
</defs>
</svg>
L'elemento <circle>
viene collocato esattamente al centro dell'immagine: cx
individua la posizione del centro sull'asse delle ascisse, mentre cy
lo colloca sull'asse delle ordinate. Il raggio r
è pari a 64px.
L'elemento <animate>
esegue un'animazione sull'attributo r
dell'elemento <circle>
; gli attributi from
e to
individuano la dimensione all'inizio e alla fine dell'animazione, begin
e dur
impostano il ritardo nell'avvio dell'animazione e la relativa durata, fill
stabilisce la forma assunta dal <circle>
alla fine dell'animazione, qui congelata all'ultimo fotogramma. Infine, repeatCount
imposta il numero di ripetizioni.
Se volessimo animare contemporaneamente più attributi, basterebbe aggiungere un elemento <animate>
per ognuno di essi, come nel listato che segue:
<rect x="256" y="168" width="0" height="0">
<animate attributeName="x" attributeType="XML" from="256" to="0" begin="0s" dur="2s" fill="freeze" repeatCount="1" />
<animate attributeName="y" attributeType="XML" from="168" to="0" begin="0s" dur="2s" fill="freeze" repeatCount="1" />
<animate attributeName="width" attributeType="XML" from="0" to="512" begin="0s" dur="2s" fill="freeze" repeatCount="1" />
<animate attributeName="height" attributeType="XML" from="0" to="336" begin="0s" dur="2s" fill="freeze" repeatCount="1" />
</rect>
In questo caso, vengono modificati tutti e quattro gli attributi dell'elemento <rect>
.
Per un bug di Webkit, l'animazione non sarà eseguita sulle attuali versioni di Chrome e Safari.
Il supporto dei browser
Tutti i browser supportano la proprietà clip
(compreso Opera). Tutti i browser dovrebbero supportare la proprietà clip-path
su elementi SVG, ma solo Firefox al momento supporta il ritaglio su elementi HTML.
Solo Firefox supporta (parzialmente) la proprietà clip-path
come riferimento ad un elemento <clipPath>
, ma limitatamente alla forma <path>
; nessun supporto per le forme base.
Chrome e Safari supportano la proprietà clip-path
, con il ricorso al prefisso -webkit-
, e il riferimento all'elemento <clipPath>
, anche con il ricorso alle forme base.
In conclusione, il supporto delle nuove tecnologie è molto frammentato e limitato a Firefox e ai due maggiori browser che utilizzano il motore Webkit. Tuttavia le potenzialità della proprietà clip-path
e del corrispondente elemento SVG sono notevoli e vale certamente la pena cominciare a sperimentare e tenere sotto controllo l'evoluzione che certamente seguirà il rilascio delle prossime versioni dei browser.