Cách tôi tạo một trò chơi giải đố CSS thuần túy PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

Cách tôi tạo một trò chơi xếp hình CSS thuần túy

Gần đây tôi đã khám phá ra niềm vui khi tạo ra các trò chơi chỉ có CSS. Thật hấp dẫn khi HTML và CSS có khả năng xử lý logic của toàn bộ trò chơi trực tuyến, vì vậy tôi đã phải thử nó! Những trò chơi như vậy thường dựa vào Checkbox Hack nơi chúng tôi kết hợp trạng thái đã chọn / không kiểm tra của đầu vào HTML với :checked lớp giả trong CSS. Chúng ta có thể làm được rất nhiều điều kỳ diệu với một sự kết hợp đó!

Trên thực tế, tôi đã thử thách bản thân để xây dựng toàn bộ trò chơi mà không có Hộp kiểm. Tôi không chắc liệu nó có khả thi không, nhưng chắc chắn là như vậy, và tôi sẽ chỉ cho bạn cách thực hiện.

Ngoài trò chơi xếp hình mà chúng ta sẽ nghiên cứu trong bài viết này, tôi đã thực hiện một bộ sưu tập các trò chơi CSS thuần túy, hầu hết trong số họ không có Checkbox Hack. (Họ cũng có sẵn trên CodePen.)

Bạn muốn chơi trước khi chúng ta bắt đầu?

Cá nhân tôi thích chơi trò chơi ở chế độ toàn màn hình, nhưng bạn có thể chơi nó ở bên dưới hoặc mở nó ra đây.

Tuyệt vời phải không? Tôi biết, nó không phải là Trò chơi xếp hình hay nhất mà bạn từng thấy ™ nhưng nó cũng không tệ chút nào đối với một thứ chỉ sử dụng CSS và một vài dòng HTML. Bạn có thể dễ dàng điều chỉnh kích thước của lưới, thay đổi số lượng ô để kiểm soát mức độ khó và sử dụng bất kỳ hình ảnh nào bạn muốn!

Chúng ta sẽ cùng nhau làm lại bản demo đó, sau đó thêm một chút lấp lánh vào cuối cho một số cú hích.

Chức năng kéo và thả

Mặc dù cấu trúc của câu đố khá đơn giản với CSS Grid, khả năng kéo và thả các mảnh ghép phức tạp hơn một chút. Tôi đã phải dựa vào sự kết hợp của các hiệu ứng chuyển tiếp, hiệu ứng di chuột và các bộ chọn anh chị em để hoàn thành công việc.

Nếu bạn di chuột qua ô trống trong bản trình diễn đó, hình ảnh sẽ di chuyển vào bên trong nó và vẫn ở đó ngay cả khi bạn di chuyển con trỏ ra khỏi hộp. Bí quyết là thêm thời lượng và độ trễ chuyển tiếp lớn - lớn đến mức hình ảnh mất nhiều thời gian để trở lại vị trí ban đầu.

img {
  transform: translate(200%);
  transition: 999s 999s; /* very slow move on mouseout */
}
.box:hover img {
  transform: translate(0);
  transition: 0s; /* instant move on hover */
}

Chỉ xác định transition-delay là đủ, nhưng việc sử dụng các giá trị lớn cho cả độ trễ và thời lượng sẽ làm giảm cơ hội người chơi thấy hình ảnh di chuyển trở lại. Nếu bạn chờ đợi 999s + 999s - khoảng 30 phút - sau đó bạn sẽ thấy hình ảnh di chuyển. Nhưng bạn sẽ không, phải không? Ý tôi là, không ai sẽ nghỉ lâu như vậy giữa các lượt trừ khi họ rời khỏi trò chơi. Vì vậy, tôi coi đây là một mẹo hay để chuyển đổi giữa hai trạng thái.

Bạn có nhận thấy rằng việc di chuột qua hình ảnh cũng kích hoạt các thay đổi không? Đó là bởi vì hình ảnh là một phần của yếu tố hộp, điều này không tốt cho chúng ta. Chúng tôi có thể sửa lỗi này bằng cách thêm pointer-events: none vào hình ảnh nhưng chúng tôi sẽ không thể kéo nó sau này.

Điều đó có nghĩa là chúng tôi phải giới thiệu một phần tử khác bên trong .box:

Bổ sung đó div (chúng tôi đang sử dụng một lớp .a) sẽ có cùng khu vực với hình ảnh (nhờ CSS Grid và grid-area: 1 / 1) và sẽ là yếu tố kích hoạt hiệu ứng di chuột. Và đó là lúc bộ chọn anh chị em hoạt động:

.a {
  grid-area: 1 / 1;
}
img {
  grid-area: 1 / 1;
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

Di chuột trên .a phần tử di chuyển hình ảnh và vì nó đang chiếm tất cả không gian bên trong hộp, thay vào đó, nó giống như chúng ta đang di chuột qua hộp! Di chuột vào hình ảnh không còn là vấn đề!

Hãy kéo và thả hình ảnh của chúng ta vào bên trong hộp và xem kết quả:

Bạn có thấy điều đó không? Đầu tiên, bạn lấy hình ảnh và chuyển nó vào hộp, không có gì lạ mắt. Nhưng khi bạn thả hình ảnh, bạn sẽ kích hoạt hiệu ứng di chuột để di chuyển hình ảnh và sau đó chúng tôi mô phỏng tính năng kéo và thả. Nếu bạn thả chuột ra bên ngoài hộp, không có gì xảy ra.

Rất tiếc, mô phỏng của bạn không hoàn hảo vì chúng tôi cũng có thể di chuột qua hộp và nhận được hiệu ứng tương tự.

Đúng và chúng tôi sẽ cải chính điều này. Chúng ta cần tắt hiệu ứng di chuột và chỉ cho phép nó khi chúng ta giải phóng hình ảnh bên trong hộp. Chúng tôi sẽ chơi với kích thước của .a yếu tố để biến điều đó thành hiện thực.

Bây giờ, di chuột qua hộp không làm gì cả. Nhưng nếu bạn bắt đầu kéo hình ảnh, .a phần tử xuất hiện và sau khi được giải phóng bên trong hộp, chúng ta có thể kích hoạt hiệu ứng di chuột và di chuyển hình ảnh.

Hãy mổ xẻ đoạn mã:

.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;
}

Nhấp vào hình ảnh sẽ kích hoạt :active lớp giả tạo ra .a full-width của phần tử (ban đầu nó bằng 0). Trạng thái hoạt động sẽ vẫn hoạt động cho đến khi chúng tôi phát hành hình ảnh. Nếu chúng tôi phát hành hình ảnh bên trong hộp, .a phần tử quay trở lại width: 0, nhưng chúng tôi sẽ kích hoạt hiệu ứng di chuột trước khi nó xảy ra và hình ảnh sẽ rơi vào bên trong hộp! Nếu bạn phát hành nó ra bên ngoài hộp, không có gì xảy ra.

Có một điều kỳ quặc nhỏ: việc nhấp vào ô trống cũng di chuyển hình ảnh và phá vỡ tính năng của chúng tôi. Hiện nay, :active được liên kết với .box phần tử, vì vậy nhấp vào nó hoặc bất kỳ phần tử con nào của nó sẽ kích hoạt nó; và bằng cách làm điều này, chúng tôi sẽ hiển thị .a và kích hoạt hiệu ứng di chuột.

Chúng tôi có thể khắc phục điều đó bằng cách chơi với pointer-events. Nó cho phép chúng tôi vô hiệu hóa bất kỳ tương tác nào với .box trong khi vẫn duy trì các tương tác với các phần tử con.

.box {
  pointer-events: none;
}
.box * {
  pointer-events: initial;
}

Hiện nay tính năng kéo và thả của chúng tôi là hoàn hảo. Trừ khi bạn có thể tìm cách hack nó, cách duy nhất để di chuyển hình ảnh là kéo và thả nó vào bên trong hộp.

Xây dựng lưới câu đố

Đặt câu đố lại với nhau sẽ cảm thấy dễ dàng hơn so với những gì chúng tôi chỉ làm cho tính năng kéo và thả. Chúng tôi sẽ dựa vào lưới CSS và các thủ thuật nền để tạo ra câu đố.

Đây là lưới của chúng tôi, được viết bằng Pug để thuận tiện:

- 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") 

Mã có thể trông lạ nhưng nó biên dịch thành HTML thuần túy:

<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>

Tôi cá là bạn đang tự hỏi điều gì xảy ra với những thẻ đó. Không có yếu tố nào trong số này có bất kỳ ý nghĩa đặc biệt nào - tôi chỉ thấy rằng mã dễ viết hơn nhiều bằng cách sử dụng <z> hơn một loạt <div class="z"> hay bất cứ cái gì.

Đây là cách tôi đã lập bản đồ cho chúng:

  • <g> là vùng chứa lưới của chúng tôi có chứa N*N <z> yếu tố.
  • <z> đại diện cho các mục lưới của chúng tôi. Nó đóng vai trò của .box phần tử chúng ta đã thấy trong phần trước.
  • <a> kích hoạt hiệu ứng di chuột.
  • <b> đại diện cho một phần hình ảnh của chúng tôi. Chúng tôi áp dụng draggable thuộc tính trên nó vì nó không thể được kéo theo mặc định.

Được rồi, hãy đăng ký vùng chứa lưới của chúng tôi trên <g>. Đây là trong Sass thay vì 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);
}

Chúng tôi thực sự sẽ tạo ra những đứa trẻ lưới của chúng tôi - <z> các phần tử - lưới cũng như có cả hai <a><b> trong cùng một khu vực lưới:

z {
  aspect-ratio: 1;
  display: grid;
  outline: 1px dashed;
}
a {
  grid-area: 1/1;
}
b {
  grid-area: 1/1;
}

Như bạn có thể thấy, không có gì lạ mắt - chúng tôi đã tạo một lưới với kích thước cụ thể. Phần còn lại của CSS chúng ta cần là tính năng kéo và thả, yêu cầu chúng ta đặt các mảnh xung quanh bảng một cách ngẫu nhiên. Tôi sẽ chuyển sang Sass cho điều này, một lần nữa để thuận tiện cho việc có thể lặp lại và tạo kiểu cho tất cả các mảnh ghép bằng một chức năng:

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)
   }
}

Bạn có thể nhận thấy rằng tôi đang sử dụng Sass random() hàm số. Đó là cách chúng tôi lấy vị trí ngẫu nhiên cho các mảnh ghép. Hãy nhớ rằng chúng tôi sẽ vô hiệu hóa vị trí đó khi di chuột qua <a> sau khi kéo và thả phần tử tương ứng của nó <b> phần tử bên trong ô lưới.

z a:hover ~ b {
  transform: translate(0);
  transition: 0s;
}

Trong cùng vòng lặp đó, tôi cũng đang xác định cấu hình nền cho từng mảnh ghép. Tất cả chúng sẽ chia sẻ một cách hợp lý cùng một hình ảnh làm nền và kích thước của nó phải bằng kích thước của toàn bộ lưới (được xác định với --s Biến đổi). Sử dụng giống nhau background-image và một số toán học, chúng tôi cập nhật background-position để chỉ hiển thị một phần của hình ảnh.

Đó là nó! Trò chơi giải đố chỉ có CSS ​​của chúng tôi được thực hiện về mặt kỹ thuật!

Nhưng chúng ta luôn có thể làm tốt hơn, phải không? Tôi chỉ cho bạn làm thế nào để tạo ra một lưới các hình dạng mảnh ghép trong một bài báo khác. Hãy lấy cùng một ý tưởng đó và áp dụng nó ở đây, phải không?

Hình dạng mảnh ghép

Đây là trò chơi giải đố mới của chúng tôi. Chức năng tương tự nhưng với hình dạng thực tế hơn!

Đây là hình minh họa của các hình dạng trên lưới:

Cách tôi tạo một trò chơi xếp hình CSS thuần túy

Nếu bạn quan sát kỹ, bạn sẽ nhận thấy rằng chúng ta có chín hình dạng mảnh ghép khác nhau: bốn góc, Các bốn cạnhmột cho mọi thứ khác.

Lưới các mảnh ghép mà tôi đã tạo trong bài viết khác mà tôi đã đề cập đến đơn giản hơn một chút:

Chúng ta có thể sử dụng cùng một kỹ thuật kết hợp mặt nạ CSS và gradient để tạo ra các hình dạng khác nhau. Trong trường hợp bạn không quen với mask và độ dốc, tôi thực sự khuyên bạn nên kiểm tra trường hợp đơn giản đó để hiểu rõ hơn về kỹ thuật trước khi chuyển sang phần tiếp theo.

Đầu tiên, chúng ta cần sử dụng các bộ chọn cụ thể để nhắm mục tiêu từng nhóm phần tử có cùng hình dạng. Chúng tôi có chín nhóm, vì vậy chúng tôi sẽ sử dụng tám bộ chọn, cộng với một bộ chọn mặc định để chọn tất cả chúng.

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 */

Dưới đây là một hình cho thấy cách ánh xạ đó với lưới của chúng tôi:

Cách tôi tạo một trò chơi giải đố CSS thuần túy PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.
Cách tôi tạo một trò chơi xếp hình CSS thuần túy

Bây giờ chúng ta hãy giải quyết các hình dạng. Hãy tập trung vào việc chỉ học một hoặc hai hình vì chúng đều sử dụng cùng một kỹ thuật - và bằng cách đó, bạn có một số bài tập về nhà để tiếp tục học!

Đối với các mảnh ghép ở giữa lưới, 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);

Mã có thể trông phức tạp, nhưng chúng ta hãy tập trung vào một gradient tại một thời điểm để xem điều gì đang xảy ra:

Hai gradient tạo ra hai vòng tròn (được đánh dấu màu xanh lá cây và màu tím trong bản demo) và hai gradient khác tạo ra các khe mà các phần khác kết nối với nhau (hình được đánh dấu màu xanh dương lấp đầy phần lớn hình dạng trong khi hình được đánh dấu màu đỏ lấp đầy phần trên cùng). Một biến CSS, --r, thiết lập bán kính của các hình tròn.

Cách tôi tạo một trò chơi giải đố CSS thuần túy PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.
Cách tôi tạo một trò chơi xếp hình CSS thuần túy

Hình dạng của các mảnh ghép ở trung tâm (được đánh dấu 0 trong hình minh họa) là khó làm nhất vì nó sử dụng bốn độ dốc và có bốn độ cong. Tất cả các mảnh khác sắp xếp ít độ dốc hơn.

Ví dụ: các mảnh ghép dọc theo cạnh trên của câu đố (được đánh dấu 2 trong hình minh họa) sử dụng ba gradient thay vì bốn:

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);

Chúng tôi đã loại bỏ gradient đầu tiên (trên cùng) và điều chỉnh các giá trị của gradient thứ hai để nó bao phủ không gian bị bỏ lại. Bạn sẽ không nhận thấy sự khác biệt lớn trong mã nếu bạn so sánh hai ví dụ. Cần lưu ý rằng chúng ta có thể tìm các cấu hình nền khác nhau để tạo ra hình dạng giống nhau. Nếu bạn bắt đầu chơi với gradient, chắc chắn bạn sẽ nghĩ ra điều gì đó khác với những gì tôi đã làm. Bạn thậm chí có thể viết một cái gì đó ngắn gọn hơn - nếu vậy, hãy chia sẻ nó trong phần bình luận!

Ngoài việc tạo các hình dạng, bạn cũng sẽ thấy rằng tôi đang tăng chiều rộng và / hoặc chiều cao của các phần tử như bên dưới:

height: calc(100% + var(--r));
width: calc(100% + var(--r));

Các mảnh ghép cần phải tràn ô lưới của chúng để kết nối.

Cách tôi tạo một trò chơi giải đố CSS thuần túy PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.
Cách tôi tạo một trò chơi xếp hình CSS thuần túy

Bản trình diễn cuối cùng

Đây là bản demo đầy đủ một lần nữa. Nếu bạn so sánh nó với phiên bản đầu tiên, bạn sẽ thấy cấu trúc mã giống nhau để tạo lưới và tính năng kéo và thả, cộng với mã để tạo các hình dạng.

Các cải tiến có thể có

Bài viết kết thúc ở đây nhưng chúng tôi có thể tiếp tục nâng cao câu đố của mình với nhiều tính năng hơn nữa! Làm thế nào về aa hẹn giờ? Hoặc có thể là một số lời chúc mừng khi người chơi hoàn thành câu đố?

Tôi có thể xem xét tất cả các tính năng này trong phiên bản tương lai, vì vậy theo dõi repo GitHub của tôi.

Kết thúc

CSS không phải là một ngôn ngữ lập trình, họ nói. Ha!

Tôi không cố gắng châm ngòi cho một số #HotDrama bằng cách đó. Tôi nói điều đó bởi vì chúng tôi đã làm một số công cụ logic thực sự phức tạp và bao gồm rất nhiều thuộc tính và kỹ thuật CSS trong quá trình thực hiện. Chúng tôi đã chơi với CSS Grid, chuyển tiếp, tạo mặt nạ, gradient, bộ chọn và thuộc tính nền. Chưa kể đến một số thủ thuật Sass mà chúng tôi đã sử dụng để làm cho mã của chúng tôi dễ dàng điều chỉnh.

Mục đích không phải là xây dựng trò chơi mà là để khám phá CSS và khám phá các thuộc tính và thủ thuật mới mà bạn có thể sử dụng trong các dự án khác. Tạo một trò chơi trực tuyến trong CSS là một thử thách thúc đẩy bạn khám phá các tính năng CSS một cách chi tiết và học cách sử dụng chúng. Thêm vào đó, rất vui khi chúng tôi có được thứ gì đó để chơi khi tất cả đã được nói và làm xong.

Cho dù CSS có phải là một ngôn ngữ lập trình hay không, không thay đổi được thực tế là chúng ta luôn học hỏi bằng cách xây dựng và tạo ra những thứ sáng tạo.

Dấu thời gian:

Thêm từ Thủ thuật CSS