Der folgende Beitrag ist ein Gastbeitrag von Ana Tudor. Vielleicht kennen Sie Ana von ihrer erstaunlichen Arbeit, die Code, Mathematik und Kunst kombiniert. Hier zeigt sie uns, wie wir das normale Verhalten von Clipping-Pfaden ändern können, indem wir clevere Geometrie anwenden, und wie wir dies dann über verschiedene Technologien und Browser hinweg nutzen können.
Die clip-path Eigenschaft in CSS wird verwendet, um Bereiche eines Elements außerhalb des Pfades auszublenden. Wir können aber auch einen Bereich innerhalb des Elements auf diese Weise ausschneiden. Das Gleiche kann durch Maskierung durch Umkehrung der Füllfarben erreicht werden, aber mein Ziel hier ist es, zu zeigen, wie dieser Effekt mit clip-path erzielt werden kann.
Der Pfad muss zwei Teile haben
- einen äußeren, der sicherstellt, dass der Bereich darin sichtbar bleibt
- einen inneren, um einen Teil des Bereichs innerhalb des äußeren Pfades zu entfernen
Die beiden müssen durch einen **Tunnel mit Nullbreite** verbunden sein. Egal, was das Auge zu sehen glaubt, alles befindet sich immer noch innerhalb des Pfades.
Die untenstehende Demo zeigt, wie der Pfad gezeichnet wird und wie die Vergrößerung der Tunnelbreite offenbart, dass das, was wie ein Rand aussieht, tatsächlich der Bereich innerhalb des Pfades ist. Beachten Sie, wie der äußere Teil gegen den Uhrzeigersinn gezeichnet wird, während der innere im Uhrzeigersinn gezeichnet wird.
Okay, schauen wir uns an, wie wir das mit CSS machen können. Die einzige grundlegende Form, die wir verwenden können, ist polygon(). Also werden wir etwas Ähnliches haben wie dieses
clip-path: polygon(
/* points of the outer triangle going anticlockwise */
285px 150px, 83px 33px, 83px 267px,
/* return to the first point of the outer triangle */
285px 150px,
/* points of the inner triangle going clockwise */
258px 150px, 96px 244px, 96px 56px,
/* return to the first point of the inner triangle */
258px 150px
);
Sie können es in diesem Pen sehen (WebKit/Blink-Browser, Firefox 47-48 mit gesetztem layout.css.clip-path-shapes.enabled auf true in about:config und Firefox 49+ out-of-the-box, kein Flag notwendig)
Das ist viel Code, der manuell geschrieben werden muss. Und es ist viel mehr Code, wenn wir statt eines Dreiecks ein Dodekagon haben wollen. Deshalb können wir Sass verwenden, um die automatische Generierung der Punktliste für regelmäßige Polygone (Polygone mit gleichen Kanten und gleichen Eckpunkten) zu automatisieren.
Schauen wir uns an, wie das funktioniert.
Wie Sie in der untenstehenden Demo sehen können, liegen alle Eckpunkte eines regelmäßigen Polygons auf einem Kreis, der Umkreis genannt wird.
Um ein regelmäßiges Polygon mit n Kanten/Eckpunkten zu erhalten, müssen wir die n Eckpunkte gleichmäßig auf einem Kreis verteilen und dann den ersten mit dem zweiten, den zweiten mit dem dritten usw. verbinden, bis wir den letzten zurück zum ersten verbinden. Aber wie verteilen wir die Punkte auf dem Kreis?
Die Position eines Punktes auf einem Kreis kann durch den Radius des Kreises und den Winkel beschrieben werden, der zwischen dem Radius, der den Punkt mit dem Mittelpunkt des Kreises verbindet, und dem Radius entlang der x-Achse in positiver Richtung gebildet wird.
Wie die obige interaktive Demo zeigt, ist die x-Koordinate der Radius multipliziert mit dem Kosinus des Winkels, den er mit der Horizontalen bildet. Die y-Koordinate ist der Radius multipliziert mit dem Sinus desselben Winkels. Wir nehmen den äußeren Radius als beliebigen Wert, der höchstens die Hälfte der minimalen Abmessung des zuzuschneidenden Elements beträgt, und den inneren etwas kleiner. Aber was ist der Winkel für jeden Eckpunkt?
Wenn wir den ersten Eckpunkt im Winkel 0 platzieren und alle Eckpunkte gleichmäßig auf dem Kreis verteilen, und es 360 Grad um den Kreis gibt, dann ist der Winkel für den i-ten Eckpunkt eines Polygons mit insgesamt n Eckpunkten i*360deg/n.
Unser Sass-Code beginnt also damit, einen Wert von mindestens 3 für die Anzahl der Eckpunkte festzulegen, den Grundwinkel (360deg/n) zu berechnen, die Variablen für den äußeren und inneren Radius zu erstellen, die Variablen für die x- und y-Verschiebung – diese geben die Position des Umkreismittelpunkts an – und die leeren Punktlisten sowohl für das äußere als auch für das innere Dreieck.
$n: 3;
$base-angle: 360deg/n;
$r-outer: 150px;
$r-inner: 120px;
$offset-x: 50%;
$offset-y: 50%;
$points-inner: ();
$points-outer: ();
Dann durchlaufen wir die Eckpunkte, berechnen die Koordinaten für jeden und fügen sie der entsprechenden Liste hinzu
@for $i from 0 through $n {
$points-outer: append(
/* list of points for the outer polygon*/
$points-outer,
/* x coordinate of current outer vertex */
calc(#{$offset-x} + #{$r-outer*cos(-$i*$base-angle)})
/* y coordinate of current outer vertex */
calc(#{$offset-y} + #{$r-outer*sin(-$i*$base-angle)}),
comma) !global;
$points-inner: append(
/* list of points for the inner polygon*/
$points-inner,
/* x coordinate of current inner vertex */
calc(#{$offset-x} + #{$r-inner*cos($i*$base-angle)})
/* y coordinate of current inner vertex */
calc(#{$offset-y} + #{$r-inner*sin($i*$base-angle)}),
comma) !global;
}
Hier gibt es ein paar wichtige Dinge zu beachten. Erstens, beim Durchlaufen, haben wir $i von 0 bis $n, was bedeutet, dass wir $n + 1 Sätze von Koordinaten zu unseren Listen hinzufügen. Das ist kein Fehler, denn sowohl für das äußere als auch für das innere Polygon müssen wir zum ersten Eckpunkt zurückkehren, um sie durch den Tunnel mit Nullbreite zu verbinden. Zweitens ist der aktuelle Winkel für jeden Eckpunkt -$i*$base-angle im Fall des äußeren Polygons und $i*$base-angle im Fall des inneren Polygons. Das liegt daran, dass das äußere gegen den Uhrzeigersinn und das innere im Uhrzeigersinn verläuft.
Zu diesem Zeitpunkt haben wir zwei Listen, die die Eckpunkte des äußeren Polygons und die des inneren Polygons enthalten. Jetzt müssen wir sie nur noch verbinden, wenn wir sie in die Polygon-Funktion einfügen.
clip-path: polygon(join($points-outer, $points-inner));
Sie können damit live in diesem Pen spielen. Ändern Sie die Werte von $n, um zu sehen, wie sich das Polygon ändert (WebKit/Blink-Browser, Firefox 47-48 mit gesetztem layout.css.clip-path-shapes.enabled auf true in about:config und Firefox 49+ out-of-the-box, kein Flag notwendig)
Wie wir es früher gemacht haben
Der folgende Teil ist aus historischen Gründen des Webs erhalten geblieben, ist aber jetzt veraltet. Zum Zeitpunkt der Erstellung des Artikels unterstützte Firefox clip-path nur mit einem SVG-Referenzwert, und die Unterstützung für Internet Explorer/Edge (vor Chromium) – die clip-path auf HTML-Elementen nie unterstützt haben – war eine übliche Anforderung. Wenn Sie nicht daran interessiert sind, wie wir die Dinge Anfang 2015 machen mussten, können Sie zu den letzten drei Demos springen!
Für Firefox müssen wir eine Referenz auf ein SVG -Element verwenden. Das würden wir im HTML platzieren
<svg width="0" height="0">
<defs>
<clipPath id="cp">
<path d="M285,150 L83,33 L83,267
L285,150
L258,150 L96,244 L96,56
L258,150z"></path>
</clipPath>
</defs>
</svg>
Hier enthält die erste Zeile der Pfaddaten die Koordinaten der äußeren Polygon-Eckpunkte, die zweite Zeile die Koordinaten ihres ersten Eckpunkts, die dritte Zeile enthält die Koordinaten der inneren Polygon-Eckpunkte und die vierte Zeile wiederholt die Koordinaten des ersten Eckpunkts dieses zweiten Polygons.
Und im CSS werden wir einfach haben
clip-path: url(#cp);
Das funktioniert jetzt in Firefox, wie Sie in dieser Demo sehen können
Es gibt jedoch eine Reihe von Dingen, die Sie hier beachten müssen.
Wir können keine Einheiten mischen, wie wir es innerhalb der polygon()-Funktion können. Die Zahlen in den Pfaddaten sind fest codiert und überhaupt nicht flexibel. Außerdem, wie bereits erwähnt wurde, nimmt Chrome/Opera den Punkt 0,0 für unser clip-path als obere linke Ecke des Bildschirms, nicht als obere linke Ecke des zuzuschneidenden Elements. Wir können dies umgehen, indem wir den Wert des clipPathUnits-Attributs von userSpaceOnUse (Standard) auf objectBoundingBox ändern. Dadurch müssen wir die Werte im Pfad ändern, die jetzt alle auf das Intervall [0, 1] skaliert werden müssen. Das Ändern von Transformationen am zugeschnittenen Element ist in Chrome immer noch fehlerhaft
Um dies flexibel zu gestalten, müssen wir mit JavaScript nur das nachbilden, was wir mit Sass getan haben.
var n = 3,
base_angle = 2*Math.PI/n,
r_outer = .5,
r_inner = .4,
offset_x = .5,
offset_y = .5,
points_outer = '',
points_inner = '',
angle, x, y;
for(var i = 0; i <= n; i++) {
angle = i*base_angle;
x = Math.cos(angle);
y = Math.sin(angle);
points_outer += ((i === 0)?'M':' L') +
(offset_x + r_outer*x).toFixed(3) + ', ' +
(offset_y - r_outer*y).toFixed(3);
points_inner += ' L' +
(offset_x + r_inner*x).toFixed(3) + ', ' +
(offset_y + r_inner*y).toFixed(3);
}
document.querySelector('#cp path').setAttribute('d', points_outer + points_inner + 'z');
Wenn Sie sich fragen, warum wir - r_outer*y haben, während die anderen drei ähnlichen Terme mit + sind, liegt das wieder daran, dass die äußeren und inneren Polygone in entgegengesetzte Richtungen gezeichnet werden müssen (hier als gegen den Uhrzeigersinn für das äußere Polygon und im Uhrzeigersinn für das innere gewählt). Das bedeutet, dass wir -angle bei jedem Schritt verwenden sollten, um die Positionen der Eckpunkte für das äußere Polygon zu berechnen, und angle für das innere. Aber cos(angle) = cos(-angle) (gleiches Vorzeichen), während sin(angle) = -sin(-angle) (entgegengesetzte Vorzeichen). Da wir cos(angle) in x und sin(angle) in y gespeichert haben, haben wir die Koordinaten des aktuellen Punktes relativ zur Verschiebung: r_outer*x, r_outer*-y für das äußere Polygon und r_inner*x, r_inner*y für das innere.
Sie können dies alles live in diesem Pen ausprobieren (Ändern des Werts der Variable n ändert das gesamte Polygon)
Wenn Sie das gleiche visuelle Ergebnis auch in IE erzielen möchten, sollte das zugeschnittene Element ein SVG-Element sein. Die anfängliche Markierung wird
<svg width="300" height="300">
<defs>
<clipPath id="cp" clipPathUnits="objectBoundingBox">
<path d=""></path>
</clipPath>
</defs>
<rect class="clip-me" width="300" height="300"></rect>
</svg>
Und Sie können damit in diesem Pen spielen
Demos
Es gibt noch mehr Dinge, für die Sie die Methode mit dem Nullbreiten-Tunnel verwenden können. Ein Beispiel ist das Ausschneiden eines Bereichs in der Mitte eines Elements.
Oder Sie könnten es verwenden, um alles außer einigen nicht verbundenen Bereichen auszuschneiden. Dies ist ohnehin recht einfach zu erreichen, wenn man eine Referenz auf ein -Element verwendet, aber ein Nullbreiten-Tunnel zwischen Regionen ist der einzige Weg, dieses Ergebnis zu erzielen, wenn man den polygon()-Wert in CSS verwendet.
Und natürlich gibt es immer die Möglichkeit, mehrere Polygone mit dem ausgeschnittenen Inneren zu haben.
Wow, jedes Mal, wenn ich so etwas sehe, bin ich immer mehr erstaunt, was in naher Zukunft allein mit CSS möglich ist.
Und ich kann mir auch immer mehr den Kopf anstoßen, weil ich im Mathematikunterricht nicht genug aufgepasst habe, um diesen CSS-Kung-Fu wirklich zu verstehen.
Ich auch… weiß jemand eine Treehouse-ähnliche Seite, auf der man seine eingerosteten Mathekenntnisse auffrischen kann… Nach einigen aktuellen Beiträgen hier habe ich das Gefühl, dass ich meine Mathekenntnisse wirklich auffrischen muss, um im Spiel zu bleiben…
Oh, toller Beitrag auch…
@Michael Whyte, hast du dir KhanAcademy.org angesehen? Es ist kostenlos.
Auch wenn ich beruflich Frontend-Programmierung mache, könnte ich nach diesem Beitrag die Frage „Kennst du dich mit CSS aus?“ bei einem Polygraphen ehrlich mit „Nein!“ beantworten.
Gehirn geblasen! Phänomenale Arbeit.
Ebenso! Ich sehe Artikel wie diese und mein Selbstvertrauen als Webdesigner/Entwickler sinkt immer weiter. Ha!
Ana Tudor ist wieder dran. Ich habe eine Liste deiner Sammlungen, Ana. Großer Fan hier. Obwohl die regulären Webprojekte, die wir machen, das meiste davon nicht brauchen würden, ist es trotzdem wirklich großartig, diese im eigenen Repertoire zu haben.