Die CSS-Pseudoklasse :has() wird in vielen Browsern eingeführt, wobei Chrome und Safari sie bereits vollständig unterstützen. Sie wird oft als „Elternselektor“ bezeichnet – das heißt, wir können ein Elternelement anhand eines Kindselektors stylen – aber :has() kann uns bei weitaus mehr Problemen helfen. Eines davon ist die Neuerfindung des klickbaren Kartenmusters, das viele von uns gerne von Zeit zu Zeit verwenden.
Wir werden uns ansehen, wie :has() uns bei verknüpften Karten helfen kann, aber zuerst...
Was ist diese :has() Pseudoklasse?
Es gibt bereits einige großartige Beiträge da draußen, die ausgezeichnet erklären, was :has() ist und wofür es verwendet wird. Da es aber noch relativ neu ist, sollten wir auch hier ein paar Worte dazu sagen.
:has() ist eine relationale Pseudoklasse, die Teil des W3C Selectors Level 4 Arbeitsentwurfs ist. Dafür sind die Klammern da: Abgleich von Elementen, die mit bestimmten Kindelementen in Beziehung stehen – oder genauer gesagt, diese enthalten.
/* Matches an article element that contains an image element */
article:has(img) { }
/* Matches an article element with an image contained immediately within it */
article:has(> img) { }
Man versteht also, warum wir sie als „Eltern“-Selektor bezeichnen könnten. Aber wir können sie auch mit anderen funktionalen Pseudoklassen kombinieren, um spezifischer zu werden. Nehmen wir an, wir möchten Artikel stylen, die *keine* Bilder enthalten. Wir können die relationalen Kräfte von :has() mit den Negationskräften von :not() kombinieren, um dies zu tun.
/* Matches an article without images */
article:not(:has(img)) { }
Aber das ist erst der Anfang dessen, wie wir Kräfte kombinieren können, um mehr mit :has() zu erreichen. Bevor wir uns speziell dem Rätsel der klickbaren Karten widmen, werfen wir einen Blick auf einige Methoden, wie wir sie derzeit ohne :has() handhaben.
Wie wir aktuell klickbare Karten handhaben
Es gibt drei Hauptansätze, wie Leute heutzutage eine vollständig klickbare Karte erstellen. Um die Leistungsfähigkeit dieser Pseudoklasse vollständig zu verstehen, ist es gut, einen kleinen Überblick zu bekommen.
Der Ansatz „Link als Wrapper“
Dieser Ansatz wird ziemlich häufig verwendet. Ich nutze ihn selbst nie, aber ich habe eine schnelle Demo erstellt, um ihn zu demonstrieren.
Hier gibt es viele Bedenken, insbesondere im Hinblick auf die Barrierefreiheit. Wenn Benutzer Ihre Website mit der Rotorfunktion navigieren, hören sie den gesamten Text innerhalb dieses <a>-Elements – die Überschrift, den Text und den Link. Jemand möchte das vielleicht nicht durchhören. Wir können es besser machen. Seit HTML5 können wir Blockelemente innerhalb eines <a>-Elements verschachteln. Aber es fühlt sich für mich nie richtig an, besonders aus diesem Grund.
Pro
- Schnell zu implementieren
- Semantisch korrekt
Contra
- Bedenken hinsichtlich der Barrierefreiheit
- Text nicht auswählbar
- Viel Aufwand, um Stile zu überschreiben, die Sie für Ihre Standardlinks verwendet haben
Die JavaScript-Methode
Mithilfe von JavaScript können wir einen Link zu unserer Karte hinzufügen, anstatt ihn im Markup zu schreiben. Ich habe diese großartige CodePen-Demo von costdev gefunden, der auch den Kartentext dabei auswählbar gemacht hat.
Dieser Ansatz hat viele Vorteile. Unsere Links sind fokussierbar und wir können sogar Text auswählen. Aber beim Styling gibt es einige Nachteile. Wenn wir diese Karten animieren wollen, müssten wir beispielsweise :hover-Stile auf unserem Haupt-.card-Wrapper anwenden, anstatt auf den Link selbst. Außerdem würden wir von den Animationen nicht profitieren, wenn die Links per Tastatur fokussiert werden.
Pro
- Kann perfekt zugänglich gemacht werden
- Möglichkeit, Text auszuwählen
Contra
- Benötigt JavaScript
- Rechtsklick nicht möglich (könnte aber mit etwas zusätzlichem Skript behoben werden)
- Erfordert viel Styling an der Karte selbst, das nicht funktioniert, wenn der Link fokussiert ist
Der ::after-Selektor-Ansatz
Diese Methode erfordert, dass wir die Karte mit relativer Positionierung versehen und dann die absolute Positionierung auf den ::after-Pseudoselektor eines Links anwenden. Dies erfordert kein JavaScript und ist ziemlich einfach zu implementieren.
Hier gibt es einige Nachteile, insbesondere bei der Textauswahl. Wenn Sie keinen höheren z-index auf Ihrem „card-body“ angeben, können Sie den Text nicht auswählen. Wenn Sie dies jedoch tun, beachten Sie, dass das Klicken auf den Text Ihren Link nicht aktiviert. Ob Sie wählbaren Text wünschen oder nicht, liegt bei Ihnen. Ich denke, es kann ein UX-Problem sein, aber es hängt vom Anwendungsfall ab. Der Text ist für Screenreader weiterhin zugänglich, aber mein Hauptproblem mit der Methode sind die fehlenden Animationsmöglichkeiten.
Pro
- Einfach zu implementieren
- Zugänglicher Link ohne aufgeblähten Text
- Funktioniert bei Hover und Fokus
Contra
- Text ist nicht auswählbar
- Sie können nur den Link animieren, da dies das Element ist, über das Sie fahren.
Ein neuer Ansatz: ::after mit :has() verwenden
Nachdem wir nun die bestehenden Ansätze für klickbare Karten dargelegt haben, möchte ich zeigen, wie die Einführung von :has() in die Mischung die meisten dieser Mängel behebt.
Tatsächlich wollen wir diesen Ansatz auf dem letzten aufbauen, den wir uns angesehen haben, bei dem ::after auf dem Linkelement verwendet wird. Wir können :has() dort tatsächlich nutzen, um die Animationsbeschränkungen dieses Ansatzes zu überwinden.
Beginnen wir mit dem Markup.
<article>
<figure>
<img src="cat.webp" alt="Fluffy gray and white tabby kitten snuggled up in a ball." />
</figure>
<div clas="article-body">
<h2>Some Heading</h2>
<p>Curabitur convallis ac quam vitae laoreet. Nulla mauris ante, euismod sed lacus sit amet, congue bibendum eros. Etiam mattis lobortis porta. Vestibulum ultrices iaculis enim imperdiet egestas.</p>
<a href="#">
Read more
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</a>
</div>
</article>
Ich werde die Dinge so einfach wie möglich halten, indem ich die Elemente in CSS anstatt Klassen anspreche.
Für diese Demo werden wir der Karte beim Hovern einen Bildzoom und einen Schatten hinzufügen und den Link mit einem Pfeil animieren, der herausspringt, und dabei die Textfarbe des Links ändern. Um dies zu erleichtern, werden wir einige benutzerdefinierte Eigenschaften verwenden, die auf unserer Karte eingestellt sind. Hier ist das Grund-Styling.
/* The card element */
article {
--img-scale: 1.001;
--title-color: black;
--link-icon-translate: -20px;
--link-icon-opacity: 0;
position: relative;
border-radius: 16px;
box-shadow: none;
background: #fff;
transform-origin: center;
transition: all 0.4s ease-in-out;
overflow: hidden;
}
/* The link's ::after pseudo */
article a::after {
content: "";
position: absolute;
inset-block: 0;
inset-inline: 0;
cursor: pointer;
}
Großartig! Wir haben eine anfängliche Skalierung für das Bild (--img-scale: 1.001), die anfängliche Farbe der Kartenüberschrift (--title-color: black) und einige zusätzliche Eigenschaften hinzugefügt, die wir verwenden werden, um unseren Pfeil aus dem Link herausspringen zu lassen. Wir haben auch einen leeren Zustand für die box-shadow-Deklaration festgelegt, um ihn später zu animieren. Dies bereitet uns auf die klickbare Karte vor, also fügen wir ihr einige Zurücksetzungen und Stile hinzu, indem wir diese benutzerdefinierten Eigenschaften zu den Elementen hinzufügen, die wir animieren wollen.
article h2 {
margin: 0 0 18px 0;
font-family: "Bebas Neue", cursive;
font-size: 1.9rem;
letter-spacing: 0.06em;
color: var(--title-color);
transition: color 0.3s ease-out;
}
article figure {
margin: 0;
padding: 0;
aspect-ratio: 16 / 9;
overflow: hidden;
}
article img {
max-width: 100%;
transform-origin: center;
transform: scale(var(--img-scale));
transition: transform 0.4s ease-in-out;
}
article a {
display: inline-flex;
align-items: center;
text-decoration: none;
color: #28666e;
}
article a:focus {
outline: 1px dotted #28666e;
}
article a .icon {
min-width: 24px;
width: 24px;
height: 24px;
margin-left: 5px;
transform: translateX(var(--link-icon-translate));
opacity: var(--link-icon-opacity);
transition: all 0.3s;
}
.article-body {
padding: 24px;
}
Seien wir nett zu den Leuten und fügen auch eine Bildschirmleser-Klasse hinzu, die hinter dem Link verborgen ist.
.sr-only:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Unsere Karte beginnt ziemlich gut auszusehen. Es ist Zeit, etwas Magie hinzuzufügen. Mit der Pseudoklasse :has() können wir jetzt prüfen, ob unser Link gehovert oder fokussiert wird, dann unsere benutzerdefinierten Eigenschaften aktualisieren und einen box-shadow hinzufügen. Mit diesem kleinen CSS-Schnipsel wird unsere Karte wirklich lebendig.
/* Matches an article element that contains a hover or focus state */
article:has(:hover, :focus) {
--img-scale: 1.1;
--title-color: #28666e;
--link-icon-translate: 0;
--link-icon-opacity: 1;
box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px;
}
Sehen Sie, was da oben passiert? Jetzt erhalten wir die aktualisierten Stile, wenn *irgendein* Kindelement in der Karte gehovert oder fokussiert wird. Und obwohl das Link-Element das einzige ist, das einen Hover- oder Fokusstatus im ::after-Ansatz für klickbare Karten enthalten kann, können wir dies nutzen, um das Elternelement abzugleichen und die Übergänge anzuwenden.
Und da haben Sie es. Nur ein weiterer leistungsfähiger Anwendungsfall für den :has()-Selektor. Wir können nicht nur ein Elternelement abgleichen, indem wir andere Elemente als Argumente deklarieren, sondern wir können auch Pseudoklassen verwenden, um Eltern abzugleichen und zu stylen.
Pro
- Zugänglich
- Animierbar
- Kein JavaScript erforderlich
- Verwendet
:hoverauf dem richtigen Element
Contra
- Text ist nicht einfach auswählbar.
- Browserunterstützung ist auf Chrome und Safari beschränkt (in Firefox hinter einem Flag unterstützt).
Hier ist eine Demo, die diese Technik verwendet. Möglicherweise bemerken Sie einen zusätzlichen Wrapper um die Karte, aber das ist nur mein Herumspielen mit Container-Abfragen, was nur eines dieser anderen fantastischen Dinge ist, die in allen wichtigen Browsern eingeführt werden.
Haben Sie weitere Beispiele, die Sie teilen möchten? Andere Lösungen oder Ideen sind im Kommentarbereich mehr als willkommen.
Funktioniert das für das endgültige Beispiel und ermöglicht Ihnen denselben Animationsstil?
Das Hovern über ein Kindelement ist in diesem Fall auch ein Hover über das Elternelement, und focus-within prüft, ob irgendein Kindelement den Fokus hat.
Ich stimme zu, dass der neue Weg sauberer ist, aber es ist nicht nur mit
:hasmöglich.Stimmt, das würde in diesem Fall ziemlich dasselbe bewirken. Man bräuchte zusätzliche Stile für den Cursor zum Beispiel, aber was ist, wenn eine Ihrer Karten keinen Link enthält? Sie würden immer noch den Hover-Effekt sehen, obwohl kein Link vorhanden ist. Mit der has-Pseudoklasse können wir das sofort prüfen.
Danke – es ist immer interessant, Anwendungsfälle für
:haszu sehen.Ein paar Vorschläge
Das Minus beim „::after-Selektor-Ansatz“ „Sie können nur den Link animieren, da dies das Element ist, über das Sie fahren“ kann umgangen werden, indem
.article-wrapper:hover imgverwendet wird. Nichts sagt, dass Sie das<a>tatsächlich ansprechen müssen, um Ihre Hover-Stile anzuwenden.Ich würde auch den „Link als Wrapper“ nicht als besonders semantisch bezeichnen, da wir einen Stapel Inhalt in einen Link packen. Der „::after-Selektor-Ansatz“ und der „:has“-Ansatz erscheinen mir als die semantischeren Optionen.
Hallo! Danke für diesen Artikel! Ihr anfänglicher Markup-Codeblock für die :has()-Lösung enthält nicht das eigentliche
<a>-Element, obwohl Sie im Text darauf verweisen. Der spätere Schnipsel tut das.Ja, in der Tat :) Ich habe den Redakteur benachrichtigt.
Behoben!
Danke, ich habe so etwas noch nie gesehen, werde es definitiv für das nächste Projekt ausprobieren.
Danke für das Feedback.
Ich stelle fest, dass bei der Verwendung der Technik
.article-wrapper:hover imgimmer davon ausgegangen wird, dass ein Link vorhanden ist, der Hover-Effekt tritt immer ein, auch wenn ein Link fehlt. Hier kann die :has()-Pseudoklasse wirklich glänzen (insbesondere wenn Sie spezifischer mit:has(a:hover, a:focus) sind.Der Link als Wrapper ist semantisch korrekt, da er Blockelemente darin zulässt (was in früheren Versionen nicht der Fall war). Dies ist gemäß der HTML5-Spezifikation :) obwohl, wie Sie auch vorgeschlagen haben, keine gute Praxis in diesem Fall.
Vielen Dank für die Unterstützung der *Barrierefreiheit*! Dies ist ein sehr gut dokumentierter und durchdachter Artikel.
Es gibt nur eine Tücke, die ich sehe. Sie sollten wahrscheinlich
aria-hidden="true"zum SVG-Element innerhalb des Link-Tags hinzufügen. Einige Browser kündigen an, dass ein Bild vorhanden ist.Guter Punkt in der Tat :)