นับแต่นั้นเป็นต้นมา (ในรูปของ CSS) เราได้นำการเรียงซ้อนมาใช้ในหลากหลายประสาทสัมผัส รูปแบบของเราประกอบด้วย "Cascading Style Sheet" รวมถึงการเรียงซ้อนตัวเลือกของเราด้วย อาจตะแคงข้างได้ ในกรณีส่วนใหญ่ ราคาเสนอจะลดลง แต่ก็ไม่เคยสูงขึ้นเลย เป็นเวลาหลายปีที่เราได้จินตนาการเกี่ยวกับ "ตัวเลือกสำหรับผู้ปกครอง" ในที่สุดเกมก็จบแล้ว รูปทรงของตัวเลือกเทียม :has()
คลาส Pseudo ใน :has()
ของ CSS จะแทนองค์ประกอบหนึ่งหากตัวเลือกใดๆ ที่ส่งผ่านเป็นพารามิเตอร์ตรงกับองค์ประกอบอย่างน้อย 1 รายการ
แต่เป็นมากกว่าตัวเลือก "หลัก" ซึ่งเป็นวิธีที่ดีในการทำการตลาด แต่วิธีที่ไม่ค่อยน่าสนใจอาจเป็นตัวเลือก "สภาพแวดล้อมตามเงื่อนไข" แต่วงแหวนวงนี้ก็ไม่ได้เหมือนกัน แล้วตัวเลือก "ครอบครัว" ล่ะ
การสนับสนุนเบราว์เซอร์
ก่อนที่จะดําเนินการเพิ่มเติม คุณควรพูดถึงการสนับสนุนเบราว์เซอร์ แต่ยังไม่ถึงจุดนั้นพอดี แต่พอใกล้เข้ามาแล้ว ยังไม่รองรับ Firefox แต่อยู่ในแผนการใช้งานแล้ว แต่มีอยู่ใน Safari อยู่แล้วและมีกำหนดเปิดตัวใน Chromium 105 การสาธิตทั้งหมดในบทความนี้จะบอกคุณหากเบราว์เซอร์ที่ใช้ไม่รองรับรูปแบบดังกล่าว
วิธีใช้ :has
แล้วมีหน้าตาเป็นอย่างไร ลองพิจารณา HTML ต่อไปนี้ที่มีองค์ประกอบระดับเดียวกัน 2 รายการกับคลาส everybody
คุณจะเลือกรายการที่มีองค์ประกอบสืบทอดเป็นคลาส a-good-time
อย่างไร
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
ด้วย :has()
คุณสามารถดำเนินการดังกล่าวได้ด้วย CSS ต่อไปนี้
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
การดำเนินการนี้จะเลือกอินสแตนซ์แรกของ .everybody
และใช้ animation
ในตัวอย่างนี้ องค์ประกอบที่มีคลาส everybody
คือเป้าหมาย เงื่อนไขมีองค์ประกอบสืบทอดที่มีคลาส a-good-time
<target>:has(<condition>) { <styles> }
แต่คุณทำได้ดีกว่านั้น เนื่องจาก :has()
เปิดโอกาสมากมาย แม้แต่บางเรื่องที่อาจยังไม่เคยค้นพบ ลองพิจารณาสิ่งเหล่านี้
เลือกองค์ประกอบ figure
ที่มี figcaption
โดยตรง
css
figure:has(> figcaption) { ... }
เลือก anchor
ที่ไม่มีองค์ประกอบที่สืบทอดจาก SVG โดยตรง
css
a:not(:has(> svg)) { ... }
เลือก label
ที่มีรายการข้างเคียง input
โดยตรง ไปข้างๆ แล้ว!
css
label:has(+ input) { … }
เลือก article
ที่ img
องค์ประกอบสืบทอดไม่มี alt
ข้อความ
css
article:has(img:not([alt])) { … }
เลือก documentElement
ที่มีบางสถานะอยู่ใน DOM
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
เลือกคอนเทนเนอร์เลย์เอาต์ที่มีจำนวนเด็กเลขคี่
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
เลือกทุกรายการในตารางกริดที่ไม่ได้วางอยู่
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
เลือกคอนเทนเนอร์ที่มีองค์ประกอบที่กำหนดเอง <todo-list>
css
main:has(todo-list) { ... }
เลือกเงื่อนไขทั้งหมด/11/}ตรง
a
ใน 1 ย่อหน้ามีเงื่อนไขตรง
a
1 ย่อหน้าถูกเลือกarticle
hr
css
p:has(+ hr) a:only-child { … }
css
article:has(>h1):has(>h2) { … }
เลือก article
ที่มีชื่อตามด้วยคำบรรยาย
css
article:has(> h1 + h2) { … }
เลือก :root
เมื่อมีการทริกเกอร์สถานะอินเทอร์แอกทีฟ
css
:root:has(a:hover) { … }
เลือกย่อหน้าที่อยู่หลัง figure
ที่ไม่มี figcaption
css
figure:not(:has(figcaption)) + p { … }
คุณนึกถึงกรณีการใช้งาน :has()
ที่น่าสนใจไหม สิ่งที่น่าสนใจที่นี่คือการกระตุ้นให้คุณหักรูปแบบความคิดของคุณออก และทำให้คุณคิดว่า "ฉันจะสร้างสไตล์เหล่านี้ด้วยวิธีอื่นได้ไหม"
ตัวอย่าง
ลองมาดูตัวอย่างวิธีที่เราใช้กัน
การ์ด
สาธิตการ์ดแบบคลาสสิก เราอาจแสดงข้อมูลใดก็ได้ในการ์ด เช่น ชื่อ ชื่อรอง หรือสื่อบางอย่าง นี่คือการ์ดพื้นฐาน
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
เกิดอะไรขึ้นเมื่อคุณต้องการแนะนำสื่อ การ์ดจะแบ่งออกเป็น 2 คอลัมน์ในการออกแบบนี้ ก่อนหน้านี้ คุณอาจสร้างชั้นเรียนใหม่เพื่อแสดงลักษณะการทำงานนี้ เช่น card--with-media
หรือ card--two-columns
ชื่อชั้นเรียนเหล่านี้ไม่เพียงแต่ทำให้บิดเบือนและจดจำได้ยากเท่านั้น แต่ยังทำให้ยากต่อการจดจำและจดจำอีกด้วย
เมื่อใช้ :has()
คุณจะตรวจสอบได้ว่าการ์ดมีสื่อบางอย่างและทำสิ่งที่เหมาะสมได้ ไม่จำเป็นต้องใช้ชื่อคลาสตัวปรับแต่ง
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
และคุณไม่จำเป็นต้องวางรหัสไว้ตรงนั้น ไว้ใช้ความคิดสร้างสรรค์กัน การ์ดที่แสดงเนื้อหา "แนะนำ" จะปรับเปลี่ยนภายในเลย์เอาต์ได้อย่างไร CSS นี้จะทำให้การ์ดแนะนำมีความกว้างเต็มของเลย์เอาต์และวางไว้ที่จุดเริ่มต้นของตารางกริด
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
จะเกิดอะไรขึ้นหากการ์ดแนะนำที่มีแบนเนอร์กระดิกไปมาเพื่อดึงดูดความสนใจ
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
หลายความเป็นไปได้
แบบฟอร์ม
แล้วแบบฟอร์มล่ะ โดยขึ้นชื่อเรื่องสไตล์แต่งตัวยาก ตัวอย่างหนึ่งคืออินพุตการจัดรูปแบบและป้ายกำกับ เช่น เราส่งสัญญาณว่าช่องนั้นถูกต้องได้อย่างไร ด้วย :has()
สิ่งนี้จะง่ายขึ้นมาก เราสามารถเชื่อมต่อกับคลาสซูโดที่เกี่ยวข้องในรูปแบบที่เกี่ยวข้อง เช่น :valid
และ :invalid
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
ลองดูในตัวอย่างนี้: ลองป้อนค่าที่ถูกต้องและไม่ถูกต้อง จากนั้นจึงเปลี่ยนโฟกัสไปเรื่อยๆ
คุณยังใช้ :has()
เพื่อแสดงหรือซ่อนข้อความแสดงข้อผิดพลาดสำหรับช่องได้ด้วย นำกลุ่มฟิลด์ "อีเมล" ของเราและเพิ่มข้อความแสดงข้อผิดพลาดลงในกลุ่ม
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
โดยค่าเริ่มต้น คุณจะซ่อนข้อความแสดงข้อผิดพลาด
.form-group__error {
display: none;
}
แต่เมื่อช่องเปลี่ยนเป็น :invalid
และไม่มีโฟกัส คุณจะแสดงข้อความได้โดยไม่ต้องระบุชื่อชั้นเรียนเพิ่มเติม
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
ไม่มีเหตุผลว่าคุณไม่สามารถใส่ความแปลกใหม่ได้อย่างมีรสนิยมเมื่อผู้ใช้โต้ตอบกับแบบฟอร์ม ลองดูตัวอย่างนี้ ดูเมื่อคุณป้อนค่าที่ถูกต้องสำหรับการโต้ตอบย่อย ค่า :invalid
จะทำให้กลุ่มแบบฟอร์มสั่น แต่เฉพาะเมื่อผู้ใช้ไม่มีค่ากำหนดการเคลื่อนไหว
เนื้อหา
เราได้พูดถึงเรื่องนี้ในตัวอย่างโค้ด แต่คุณจะใช้ :has()
ในขั้นตอนการทำงานของเอกสารได้อย่างไร โดยมีแนวคิดว่าเราสามารถจัดรูปแบบการพิมพ์ในสื่ออย่างไร เป็นต้น
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
ตัวอย่างนี้มีตัวเลข เมื่อไม่มี figcaption
ก็จะลอยอยู่ภายในเนื้อหา เมื่อมี figcaption
จะใช้ความกว้างเต็มหน้าจอและจะได้ขอบเพิ่มเติม
การโต้ตอบกับสถานะ
ลองทำให้รูปแบบของคุณตอบสนองต่อบางสถานะในมาร์กอัปของเรา ดูตัวอย่างที่มีแถบนำทางแบบเลื่อนแบบ "คลาสสิก" หากคุณมีปุ่มที่เปิด/ปิดการนำทาง ปุ่มนั้นอาจใช้แอตทริบิวต์ aria-expanded
ใช้ JavaScript เพื่ออัปเดตแอตทริบิวต์ที่เหมาะสม เมื่อ aria-expanded
เท่ากับ true
ให้ใช้ :has()
เพื่อตรวจหาแล้วอัปเดตรูปแบบการนำทางแบบเลื่อน JavaScript ก็ทำหน้าที่นี้ และ CSS สามารถตอบสนองความต้องการด้วยข้อมูลดังกล่าว ไม่จำเป็นต้องสลับมาร์กอัปไปรอบๆ หรือเพิ่มชื่อคลาสเพิ่มเติม ฯลฯ (หมายเหตุ: ตัวอย่างนี้ไม่ใช่ตัวอย่างที่พร้อมสำหรับเวอร์ชันที่ใช้งานจริง)
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
: สามารถช่วยหลีกเลี่ยงข้อผิดพลาดของผู้ใช้ได้หรือไม่
ตัวอย่างเหล่านี้มีอะไรที่เหมือนกัน นอกจากข้อเท็จจริงที่ว่าช่องนี้แสดงวิธีใช้ :has()
แล้ว ยังไม่มีใครต้องแก้ไขชื่อคลาสเลย โดยแต่ละคนจะแทรกเนื้อหาใหม่และอัปเดตแอตทริบิวต์ ซึ่งถือเป็นประโยชน์ที่สำคัญของ :has()
ตรงที่ช่วยลดข้อผิดพลาดของผู้ใช้ได้ ขณะที่ CSS ของ :has()
รับหน้าที่รับผิดชอบในการปรับเปลี่ยนการแก้ไขใน DOM ได้ คุณไม่จำเป็นต้องสลับชื่อคลาสใน JavaScript ซึ่งทำให้มีโอกาสเกิดข้อผิดพลาดของนักพัฒนาซอฟต์แวร์น้อยลง พวกเราทุกคนต่างเคยเจอสถานการณ์แบบนี้เมื่อพิมพ์ชื่อชั้นเรียนผิดและต้องทำให้นักเรียนต้องค้นหาต่อไปใน Object
เป็นความคิดที่น่าสนใจและนำเราไปสู่มาร์กอัปที่ดูสะอาดตาและใช้โค้ดน้อยลงหรือไม่ JavaScript ลดลงเนื่องจากเราไม่ได้ปรับเปลี่ยน JavaScript มากนัก ลด HTML เนื่องจากคุณไม่ต้องการใช้คลาสอย่าง card card--has-media
ฯลฯ แล้ว
การคิดนอกกรอบ
ดังที่กล่าวไว้ข้างต้น :has()
สนับสนุนให้คุณหักรูปแบบความคิด เป็นโอกาสในการลองสิ่งใหม่ๆ วิธีหนึ่งในการพยายามขยายขอบเขตการทำงานคือการสร้างกลไกของเกมด้วย CSS เพียงอย่างเดียว คุณสามารถสร้างเครื่องจักรงานตามขั้นตอนด้วยแบบฟอร์มและ CSS เป็นต้น
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
ซึ่งทำให้เกิดความเป็นไปได้ที่น่าสนใจ คุณสามารถใช้ข้อมูลนั้นเพื่อข้ามผ่านแบบฟอร์มที่มีการแปลง หมายเหตุ การสาธิตนี้เหมาะที่สุดสำหรับดูในแท็บเบราว์เซอร์แยกต่างหาก
แล้วสนุกกับเกม Buzz สุดคลาสสิกนี้ล่ะ สร้างกลไกได้ง่ายกว่าด้วย :has()
หากสายไฟชี้ลงไปอีก แสดงว่าเกมจบแล้ว ได้ เราสามารถสร้างกลไกเกมเหล่านี้บางส่วนด้วยสิ่งต่างๆ เช่น ชุดค่าผสมข้างเคียง (+
และ ~
) แต่ :has()
เป็นวิธีที่จะทำให้ได้รับผลลัพธ์เดียวกันโดยไม่ต้องใช้ "กลเม็ด" มาร์กอัปที่น่าสนใจ หมายเหตุ การสาธิตนี้เหมาะที่สุดสำหรับดูในแท็บเบราว์เซอร์แยกต่างหาก
แม้ว่าคุณจะไม่ได้เปิดตัวเวอร์ชันที่ใช้งานจริงในเร็วๆ นี้ แต่แอปได้ไฮไลต์วิธีการที่คุณสามารถใช้รูปแบบพื้นฐานได้ เช่น สามารถเชน :has()
ได้
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
ประสิทธิภาพและข้อจำกัด
ก่อนจากกันไป คุณใช้ :has()
ทำอะไรได้บ้าง มีข้อจำกัดบางประการสำหรับ :has()
ปัจจัยหลักๆ มักเกิดจากการที่วิดีโอมีประสิทธิภาพลดลง
- คุณจะ
:has()
:has()
ไม่ได้ แต่คุณเชื่อมโยง:has()
ได้css :has(.a:has(.b)) { … }
- ไม่มีการใช้งานองค์ประกอบจำลองภายใน
:has()
css :has(::after) { … } :has(::first-letter) { … }
- จำกัดการใช้
:has()
ในแอปที่ยอมรับเฉพาะตัวเลือกสารประกอบcss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- จำกัดการใช้
:has()
หลังองค์ประกอบจำลองcss ::part(foo):has(:focus) { … }
- การใช้
:visited
จะเป็นเท็จเสมอcss :has(:visited) { … }
ดูเมตริกประสิทธิภาพจริงที่เกี่ยวข้องกับ :has()
ได้ที่สัญญาณรบกวนนี้ ขอขอบคุณ Byungwoo ที่แชร์ข้อมูลเชิงลึกและรายละเอียดเกี่ยวกับการใช้งาน
เท่านี้ก็เรียบร้อย
เตรียมพร้อมสำหรับ :has()
บอกเล่าให้เพื่อนฟังและแชร์โพสต์นี้เลย คราวนี้จะเป็นจุดเปลี่ยนสำหรับวิธีที่เราใช้ CSS
การสาธิตทั้งหมดอยู่ในคอลเลกชัน CodePen นี้