ฉันเพิ่งค้นพบความสุขในการสร้างเกมที่ใช้ CSS เท่านั้น เป็นเรื่องที่น่าสนใจเสมอที่ HTML และ CSS สามารถจัดการกับตรรกะของเกมออนไลน์ทั้งหมดได้ ดังนั้นฉันจึงต้องลอง! เกมดังกล่าวมักจะใช้ Checkbox Hack แบบเก่า ซึ่งเรารวมสถานะที่ตรวจสอบแล้ว/ไม่ได้เลือกของอินพุต HTML เข้ากับ :checked
คลาสหลอกใน CSS เราสร้างเวทย์มนตร์ได้มากมายด้วยชุดค่าผสมนั้น!
อันที่จริง ฉันท้าทายตัวเองให้สร้างเกมทั้งเกมโดยไม่มีช่องทำเครื่องหมาย ฉันไม่แน่ใจว่ามันจะเป็นไปได้หรือไม่ แต่มันแน่นอน และฉันจะแสดงให้คุณเห็นว่า
นอกจากเกมไขปริศนาเราจะเรียนในบทความนี้แล้ว คอลเลกชันของเกม CSS ล้วนๆส่วนใหญ่ไม่มี Checkbox Hack (นอกจากนี้ยังมี บน CodePen.)
ต้องการที่จะเล่นก่อนที่เราจะเริ่ม?
โดยส่วนตัวฉันชอบเล่นเกมในโหมดเต็มหน้าจอ แต่คุณสามารถเล่นได้ที่ด้านล่างหรือ เปิดตรงนี้.
เย็นใช่มั้ย? ฉันรู้ มันไม่ใช่เกมไขปริศนาที่ดีที่สุดที่คุณเคยเห็น™ แต่ก็ไม่ได้แย่สำหรับบางอย่างที่ใช้ CSS และ HTML เพียงไม่กี่บรรทัดเท่านั้น คุณสามารถปรับขนาดของตาราง เปลี่ยนจำนวนเซลล์เพื่อควบคุมระดับความยาก และใช้รูปภาพอะไรก็ได้ที่คุณต้องการ!
เราจะทำการรีเมคเดโมนั้นใหม่ด้วยกัน จากนั้นจึงเพิ่มประกายพิเศษเล็กน้อยในตอนท้ายเพื่อการเตะ
ฟังก์ชันลากและวาง
แม้ว่าโครงสร้างของตัวต่อจะค่อนข้างตรงไปตรงมากับ CSS Grid แต่ความสามารถในการลากและวางชิ้นส่วนของตัวต่อนั้นค่อนข้างซับซ้อน ฉันต้องอาศัยการผสมผสานระหว่างการเปลี่ยน เอฟเฟกต์โฮเวอร์ และตัวเลือกพี่น้องเพื่อให้เสร็จ
หากคุณวางเมาส์เหนือช่องว่างในการสาธิตนั้น รูปภาพจะย้ายภายในกล่องและจะยังคงอยู่แม้ว่าคุณจะเลื่อนเคอร์เซอร์ออกจากกล่องก็ตาม เคล็ดลับคือการเพิ่มระยะเวลาการเปลี่ยนภาพและการหน่วงเวลาให้มาก — ใหญ่มากจนรูปภาพต้องใช้เวลามากในการกลับสู่ตำแหน่งเริ่มต้น
img {
transform: translate(200%);
transition: 999s 999s; /* very slow move on mouseout */
}
.box:hover img {
transform: translate(0);
transition: 0s; /* instant move on hover */
}
ระบุเฉพาะ transition-delay
ก็เพียงพอแล้ว แต่การใช้ค่าจำนวนมากทั้งในการหน่วงเวลาและระยะเวลาจะลดโอกาสที่ผู้เล่นจะได้เห็นภาพเคลื่อนกลับ ถ้าคุณรอ 999s + 999s
— ซึ่งใช้เวลาประมาณ 30 นาที — จากนั้นคุณจะเห็นภาพเคลื่อนไหว แต่คุณจะไม่ใช่ไหม ฉันหมายถึง จะไม่มีใครใช้เวลานานขนาดนั้นระหว่างเทิร์น เว้นแต่พวกเขาจะเดินออกจากเกม ดังนั้น ฉันคิดว่านี่เป็นเคล็ดลับที่ดีในการสลับระหว่างสองสถานะ
คุณสังเกตเห็นว่าการวางเมาส์เหนือรูปภาพทำให้เกิดการเปลี่ยนแปลงด้วยหรือไม่ นั่นเป็นเพราะภาพเป็นส่วนหนึ่งขององค์ประกอบกล่อง ซึ่งไม่เป็นผลดีต่อเรา เราแก้ไขได้โดยเพิ่ม pointer-events: none
ไปที่รูปภาพ แต่เราไม่สามารถลากได้ในภายหลัง
นั่นหมายความว่าเราต้องแนะนำองค์ประกอบอื่นภายใน .box
:
ความพิเศษนั้น div
(เรากำลังใช้คลาสของ .a
) จะใช้พื้นที่เดียวกับรูปภาพ (ขอบคุณ CSS Grid และ grid-area: 1 / 1
) และจะเป็นองค์ประกอบที่เรียกเอฟเฟกต์โฮเวอร์ และนั่นคือสิ่งที่ตัวเลือกพี่น้องเข้ามาเล่น:
.a {
grid-area: 1 / 1;
}
img {
grid-area: 1 / 1;
transform: translate(200%);
transition: 999s 999s;
}
.a:hover + img {
transform: translate(0);
transition: 0s;
}
โฉบบน .a
องค์ประกอบจะย้ายรูปภาพ และเนื่องจากมันกินพื้นที่ทั้งหมดในกล่อง มันเหมือนกับว่าเรากำลังวางเมาส์เหนือกล่องแทน! การโฮเวอร์รูปภาพไม่ใช่ปัญหาอีกต่อไป!
ลากและวางรูปภาพของเราในกล่องและดูผลลัพธ์:
คุณเห็นไหม ก่อนอื่นคุณคว้าภาพแล้วย้ายไปยังกล่อง ไม่มีอะไรพิเศษ แต่เมื่อคุณปล่อยรูปภาพ คุณจะทริกเกอร์เอฟเฟกต์โฮเวอร์ที่ย้ายรูปภาพ จากนั้นเราจะจำลองคุณลักษณะการลากและวาง หากคุณปล่อยเมาส์นอกกรอบ จะไม่มีอะไรเกิดขึ้น
อืม การจำลองของคุณไม่สมบูรณ์แบบเพราะเราสามารถวางเมาส์เหนือกล่องและได้เอฟเฟกต์แบบเดียวกัน
จริงและเราจะแก้ไขสิ่งนี้ เราจำเป็นต้องปิดการใช้งานเอฟเฟกต์โฮเวอร์และอนุญาตก็ต่อเมื่อเราปล่อยรูปภาพในกล่อง เราจะเล่นกับมิติของเรา .a
องค์ประกอบที่จะทำให้สิ่งนั้นเกิดขึ้น
ตอนนี้ การวางเมาส์เหนือกล่องไม่ทำอะไรเลย แต่ถ้าคุณเริ่มลากภาพ .a
องค์ประกอบปรากฏขึ้น และเมื่อปล่อยภายในกล่องแล้ว เราสามารถเรียกใช้เอฟเฟกต์โฮเวอร์และย้ายรูปภาพได้
มาแยกรหัสกัน:
.a {
width: 0%;
transition: 0s .2s; /* add a small delay to make sure we catch the hover effect */
}
.box:active .a { /* on :active increase the width */
width: 100%;
transition: 0s; /* instant change */
}
img {
transform: translate(200%);
transition: 999s 999s;
}
.a:hover + img {
transform: translate(0);
transition: 0s;
}
การคลิกที่ภาพจะเป็นการยิง :active
คลาสเทียมที่ทำให้ .a
องค์ประกอบเต็มความกว้าง (เริ่มต้นเท่ากับ 0
). สถานะใช้งานจะยังคงอยู่ คล่องแคล่ว จนกว่าเราจะปล่อยภาพออกมา ถ้าเราปล่อยภาพภายในกล่อง .a
องค์ประกอบกลับไปที่ width: 0
แต่เราจะเรียกใช้เอฟเฟกต์โฮเวอร์ก่อนที่มันจะเกิดขึ้น และรูปภาพจะอยู่ในกล่อง! ถ้าปล่อยนอกกรอบก็ไม่มีอะไรเกิดขึ้น
มีมุมแหลมเล็กน้อย: การคลิกที่ช่องว่างจะเป็นการย้ายรูปภาพและทำให้คุณลักษณะของเราหยุดชะงัก ปัจจุบัน, :active
เชื่อมโยงกับ .box
องค์ประกอบดังนั้นการคลิกที่มันหรือลูก ๆ ของมันจะเป็นการเปิดใช้งาน และเมื่อทำเช่นนี้ เราก็จะแสดง .a
องค์ประกอบและเรียกเอฟเฟกต์โฮเวอร์
เราแก้ได้ด้วยการเล่นกับ pointer-events
. ช่วยให้เราสามารถปิดการใช้งานการโต้ตอบใด ๆ กับ .box
ในขณะที่ยังคงปฏิสัมพันธ์กับองค์ประกอบย่อย
.box {
pointer-events: none;
}
.box * {
pointer-events: initial;
}
คุณลักษณะการลากและวางของเราสมบูรณ์แบบ วิธีเดียวที่จะย้ายรูปภาพคือลากแล้ววางลงในกล่อง เว้นแต่ว่าคุณจะค้นพบวิธีแฮ็กได้
สร้างตารางปริศนา
การรวมตัวต่อเข้าด้วยกันจะเป็นเรื่องง่าย เมื่อเทียบกับสิ่งที่เราเพิ่งทำสำหรับคุณสมบัติการลากแล้วปล่อย เราจะใช้ตาราง CSS และเทคนิคเบื้องหลังในการสร้างปริศนา
นี่คือตารางของเราที่เขียนด้วย Pug เพื่อความสะดวก:
- let n = 4; /* number of columns/rows */
- let image = "https://picsum.photos/id/1015/800/800";
g(style=`--i:url(${image})`)
- for(let i = 0; i < n*n; i++)
z
a
b(draggable="true")
โค้ดอาจดูแปลกแต่คอมไพล์เป็น HTML ธรรมดา:
<g style="--i: url(https://picsum.photos/id/1015/800/800)">
<z>
<a></a>
<b draggable="true"></b>
</z>
<z>
<a></a>
<b draggable="true"></b>
</z>
<z>
<a></a>
<b draggable="true"></b>
</z>
<!-- etc. -->
</g>
ฉันพนันได้เลยว่าคุณกำลังสงสัยว่าเกิดอะไรขึ้นกับแท็กเหล่านั้น ไม่มีองค์ประกอบเหล่านี้มีความหมายพิเศษใด ๆ — ฉันเพิ่งพบว่าโค้ดนั้นเขียนได้ง่ายกว่ามากโดยใช้ <z>
กว่าพวงของ <div class="z">
หรืออะไรก็ตาม
นี่คือวิธีที่ฉันได้วางแผนไว้:
<g>
เป็นคอนเทนเนอร์กริดของเราที่มีN*N
<z>
องค์ประกอบ<z>
แสดงถึงรายการกริดของเรา มันเล่นบทบาทของ.box
องค์ประกอบที่เราเห็นในส่วนก่อนหน้า<a>
ทริกเกอร์เอฟเฟกต์โฮเวอร์<b>
แสดงถึงส่วนหนึ่งของภาพของเรา เราใช้draggable
แอตทริบิวต์บนเนื่องจากไม่สามารถลากโดยค่าเริ่มต้น
เอาล่ะ มาลงทะเบียนคอนเทนเนอร์กริดของเรากันเถอะ <g>
. นี่คือใน Sass แทนที่จะเป็น CSS:
$n : 4; /* number of columns/rows */
g {
--s: 300px; /* size of the puzzle */
display: grid;
max-width: var(--s);
border: 1px solid;
margin: auto;
grid-template-columns: repeat($n, 1fr);
}
เรากำลังจะสร้างลูกกริดของเราจริงๆ — the <z>
องค์ประกอบ — กริดเช่นกันและมีทั้ง <a>
และ <b>
ภายในพื้นที่กริดเดียวกัน:
z {
aspect-ratio: 1;
display: grid;
outline: 1px dashed;
}
a {
grid-area: 1/1;
}
b {
grid-area: 1/1;
}
อย่างที่คุณเห็น ไม่มีอะไรหรูหรา เราสร้างตารางที่มีขนาดเฉพาะ CSS ที่เหลือที่เราต้องการคือคุณลักษณะการลากและวาง ซึ่งกำหนดให้เราต้องสุ่มวางชิ้นส่วนต่างๆ รอบกระดาน ฉันจะหันไปหา Sass อีกครั้งเพื่อความสะดวกในการวนซ้ำและจัดรูปแบบชิ้นส่วนปริศนาทั้งหมดด้วยฟังก์ชัน:
b {
background: var(--i) 0/var(--s) var(--s);
}
@for $i from 1 to ($n * $n + 1) {
$r: (random(180));
$x: (($i - 1)%$n);
$y: floor(($i - 0.001) / $n);
z:nth-of-type(#{$i}) b{
background-position: ($x / ($n - 1)) * 100% ($y / ($n - 1)) * 100%;
transform:
translate((($n - 1) / 2 - $x) * 100%, (($n - 1)/2 - $y) * 100%)
rotate($r * 1deg)
translate((random(100)*1% + ($n - 1) * 100%))
rotate((random(20) - 10 - $r) * 1deg)
}
}
คุณอาจสังเกตเห็นว่าฉันกำลังใช้ Sass random()
การทำงาน. นั่นเป็นวิธีที่เราได้รับตำแหน่งแบบสุ่มสำหรับชิ้นส่วนปริศนา จำไว้ว่าเราจะ ปิดการใช้งาน ตำแหน่งนั้นเมื่อวางเมาส์เหนือ <a>
องค์ประกอบหลังจากลากและวางที่สอดคล้องกัน <b>
องค์ประกอบภายในเซลล์กริด
z a:hover ~ b {
transform: translate(0);
transition: 0s;
}
ในลูปเดียวกันนั้น ฉันยังกำหนดการกำหนดค่าพื้นหลังสำหรับปริศนาแต่ละชิ้นด้วย พวกเขาทั้งหมดจะแบ่งปันรูปภาพเดียวกันกับพื้นหลังอย่างมีเหตุผล และขนาดควรเท่ากับขนาดของตารางทั้งหมด (กำหนดด้วย --s
ตัวแปร). ใช้เหมือนกัน background-image
และคณิตศาสตร์บางส่วน เราอัปเดต background-position
เพื่อแสดงเพียงบางส่วนของภาพ
แค่นั้นแหละ! เกมไขปริศนา CSS เท่านั้นของเราเสร็จสิ้นในทางเทคนิคแล้ว!
แต่เราสามารถทำได้ดีกว่าเสมอใช่ไหม ฉันแสดงให้คุณเห็น วิธีทำตารางรูปทรงชิ้นส่วนปริศนา ในบทความอื่น ลองนำแนวคิดเดียวกันมาปรับใช้ที่นี่กันไหม
รูปร่างชิ้นปริศนา
นี่คือเกมไขปริศนาใหม่ของเรา ฟังก์ชันเดียวกัน แต่มีรูปร่างที่สมจริงยิ่งขึ้น!
นี่คือภาพประกอบของรูปร่างบนตาราง:
หากคุณสังเกตดีๆ คุณจะสังเกตเห็นว่าเรามีรูปทรงตัวต่อที่แตกต่างกันเก้าแบบ: the สี่มุมที่ สี่ขอบและ หนึ่งสำหรับทุกสิ่งทุกอย่าง.
ตารางของชิ้นส่วนจิ๊กซอว์ที่ฉันทำในบทความอื่นที่ฉันอ้างถึงนั้นตรงไปตรงมากว่าเล็กน้อย:
เราสามารถใช้เทคนิคเดียวกับที่รวมมาสก์ CSS และการไล่ระดับสีเพื่อสร้างรูปร่างที่แตกต่างกัน ในกรณีที่คุณไม่คุ้นเคย mask
และการไล่ระดับสี ฉันขอแนะนำให้ตรวจสอบเป็นอย่างยิ่ง กรณีที่ง่ายขึ้น เพื่อให้เข้าใจเทคนิคมากขึ้นก่อนจะไปยังส่วนต่อไป
อันดับแรก เราต้องใช้ตัวเลือกเฉพาะเพื่อกำหนดเป้าหมายแต่ละกลุ่มขององค์ประกอบที่มีรูปร่างเหมือนกัน เรามีเก้ากลุ่ม ดังนั้นเราจะใช้ตัวเลือกแปดตัว บวกกับตัวเลือกเริ่มต้นที่เลือกทั้งหมด
z /* 0 */
z:first-child /* 1 */
z:nth-child(-n + 4):not(:first-child) /* 2 */
z:nth-child(5) /* 3 */
z:nth-child(5n + 1):not(:first-child):not(:nth-last-child(5)) /* 4 */
z:nth-last-child(5) /* 5 */
z:nth-child(5n):not(:nth-child(5)):not(:last-child) /* 6 */
z:last-child /* 7 */
z:nth-last-child(-n + 4):not(:last-child) /* 8 */
ต่อไปนี้คือตัวเลขที่แสดงวิธีการแมปไปยังกริดของเรา:
ทีนี้มาจัดการรูปร่างกัน มามุ่งเน้นที่การเรียนรู้เพียงหนึ่งหรือสองรูปทรงเพราะพวกมันทั้งหมดใช้เทคนิคเดียวกัน — และด้วยวิธีนี้ คุณมีการบ้านเพื่อเรียนรู้ต่อไป!
สำหรับชิ้นส่วนจิ๊กซอว์ที่อยู่ตรงกลางตาราง 0
:
mask:
radial-gradient(var(--r) at calc(50% - var(--r) / 2) 0, #0000 98%, #000) var(--r)
0 / 100% var(--r) no-repeat,
radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% - var(--r) / 2), #0000 98%, #000)
var(--r) 50% / 100% calc(100% - 2 * var(--r)) no-repeat,
radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);
โค้ดอาจดูซับซ้อน แต่ให้เน้นที่การไล่ระดับสีทีละครั้งเพื่อดูว่าเกิดอะไรขึ้น:
การไล่ระดับสีสองแบบสร้างวงกลมสองวง (มีการทำเครื่องหมายเป็นสีเขียวและสีม่วงในการสาธิต) และการไล่ระดับสีอีกสองแบบจะสร้างช่องที่ชิ้นส่วนอื่นเชื่อมต่อด้วย (อันที่ทำเครื่องหมายสีน้ำเงินจะเติมให้เต็มรูปร่าง ในขณะที่อันที่ทำเครื่องหมายสีแดงเติมส่วนที่ด้านบนสุด) ตัวแปร CSS --r
, กำหนดรัศมีของรูปทรงกลม
รูปร่างของชิ้นส่วนจิ๊กซอว์ที่อยู่ตรงกลาง (ทำเครื่องหมาย 0
ในภาพประกอบ) เป็นสิ่งที่ทำได้ยากที่สุดเนื่องจากใช้การไล่ระดับสีสี่แบบและมีความโค้งสี่แบบ ชิ้นอื่น ๆ ทั้งหมดเล่นกลการไล่ระดับสีน้อยลง
ตัวอย่างเช่น จิ๊กซอว์ชิ้นที่อยู่ตรงขอบด้านบนของตัวต่อ (ทำเครื่องหมาย 2
ในภาพประกอบ) ใช้การไล่ระดับสีสามแบบแทนที่จะเป็นสี่แบบ:
mask:
radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% + var(--r) / 2), #0000 98%, #000) var(--r) calc(-1 * var(--r)) no-repeat,
radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);
เราลบการไล่ระดับสีแรก (บนสุด) และปรับค่าของการไล่ระดับสีที่สองเพื่อให้ครอบคลุมพื้นที่ที่เหลือ คุณจะไม่สังเกตเห็นความแตกต่างอย่างมากในโค้ดหากคุณเปรียบเทียบทั้งสองตัวอย่าง ควรสังเกตว่าเราสามารถค้นหาการกำหนดค่าพื้นหลังที่แตกต่างกันเพื่อสร้างรูปร่างเดียวกันได้ หากคุณเริ่มเล่นกับการไล่ระดับสี คุณจะได้พบกับสิ่งที่แตกต่างไปจากที่ฉันทำอย่างแน่นอน คุณอาจจะเขียนอะไรที่กระชับกว่านี้ก็ได้ ถ้าใช่ ให้แชร์ในความคิดเห็น!
นอกจากการสร้างรูปร่างแล้ว คุณยังจะพบว่าฉันกำลังเพิ่มความกว้างและ/หรือความสูงขององค์ประกอบต่างๆ ดังนี้:
height: calc(100% + var(--r));
width: calc(100% + var(--r));
ชิ้นส่วนของปริศนาต้องล้นเซลล์กริดเพื่อเชื่อมต่อ
สาธิตขั้นสุดท้าย
นี่คือการสาธิตแบบเต็มอีกครั้ง หากคุณเปรียบเทียบกับเวอร์ชันแรก คุณจะเห็นโครงสร้างโค้ดเดียวกันในการสร้างกริดและคุณลักษณะการลากแล้ววาง รวมทั้งโค้ดสำหรับสร้างรูปร่าง
การปรับปรุงที่เป็นไปได้
บทความจบลงที่นี่ แต่เราสามารถปรับปรุงปริศนาของเราต่อไปด้วยคุณสมบัติเพิ่มเติม! แล้วตัวจับเวลาล่ะ? หรืออาจแสดงความยินดีเมื่อผู้เล่นไขปริศนาเสร็จ
ฉันอาจจะพิจารณาคุณสมบัติทั้งหมดเหล่านี้ในเวอร์ชันต่อๆ ไป ดังนั้น จับตาดู repo GitHub ของฉัน.
ตัดขึ้น
และ CSS ไม่ใช่ภาษาโปรแกรมพวกเขาพูด ฮา!
ฉันไม่ได้พยายามที่จะจุดประกาย #HotDrama บางอย่างด้วยสิ่งนั้น ฉันพูดอย่างนั้นเพราะเราทำเรื่องตรรกะที่ยุ่งยากจริงๆ และครอบคลุมคุณสมบัติและเทคนิค CSS มากมายไปพร้อมกัน เราเล่นกับ CSS Grid, ทรานซิชัน, มาสก์, การไล่ระดับสี, ตัวเลือก และคุณสมบัติพื้นหลัง ไม่ต้องพูดถึงเทคนิค Sass สองสามอย่างที่เราใช้เพื่อทำให้โค้ดของเราปรับเปลี่ยนได้ง่าย
เป้าหมายไม่ใช่เพื่อสร้างเกม แต่เพื่อสำรวจ CSS และค้นพบคุณสมบัติและลูกเล่นใหม่ที่คุณสามารถใช้ในโปรเจ็กต์อื่นได้ การสร้างเกมออนไลน์ใน CSS เป็นความท้าทายที่ผลักดันให้คุณสำรวจคุณสมบัติ CSS อย่างละเอียดและเรียนรู้วิธีใช้งาน นอกจากนี้ยังเป็นเรื่องสนุกมากมายที่เราได้เล่นอะไรเมื่อพูดและทำเสร็จแล้ว
ไม่ว่า CSS จะเป็นภาษาการเขียนโปรแกรมหรือไม่ก็ตาม ไม่ได้เปลี่ยนความจริงที่ว่าเราเรียนรู้อยู่เสมอโดยการสร้างและสร้างสรรค์สิ่งใหม่ๆ