Search code examples
javascripthtmlcssz-index

How can I use z-index to simulate a chip dropping through a connect four board to give it a 3d effect?


I'm making a connect four game and right now I can drop a the chip in it's appropriate slot as well as change from a red chip to a yellow chip. However when you drop the chip it does not go into the board. It is layered outside of the board. I would like the chip that's being dropped to fall over the dark blue circle within each slot and fall under the slot itself. So it would look realistic and 3d.

I thought I would be able to do this with z-index, but I have 2 problems. 1st When I set the div slots to z-index of 3, even though the falling chip has a z-index of 2; the chip still falls over the slot? 2nd, even if that did work the dark blue circle in each slot would now be hidden because the div has a higher z-index, they would need to be the same for both of them to be visible. But if they're the same the chip cannot fall within the board?

Any ideas on how to make this effect?

//grab all slot positions on the board
const slots = document.querySelectorAll('.board div');
let player = 'p1';
let board = [ 
	0, 1, 2, 3, 4, 5, 6,
	7, 8, 9, 10, 11, 12, 13,
	14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27,
	28, 29, 30, 31, 32, 33, 34,
	35, 36, 37, 38, 39, 40, 41,
]

//assign a class to each slot to represent its position
for(let i = 0; i < slots.length; i++) {
	//add class to each div
	slots[i].classList.add('c' + i);
	//add the slot to each div
	let slot = document.createElement('span');
	slots[i].appendChild(slot);
	//add the function with the game logic to each slot
	slots[i].addEventListener('click', runGame); 
}

function runGame() {
	//figure out which column the selected slot sits in
	const slotColumn = (Number(this.className.slice(1, 3)) % 7);
	//create an array to store all the slots that share the above column
	const columnArray = [];

	//grab all the slots that sit in that column
	for(let i = 0; i < board.length; i++) {
		if(board[i] % 7 === slotColumn) columnArray.push(board[i]);
	}

	//drop chip in the chosen column
	dropChip(columnArray);

	function dropChip(column) {
		//select bottom most slot that's available in the column
		for(let i = column.length - 1; i >= 0; i--) {
			if(column[i] !== 'p1' || column[i] !== 'p2') {
				board[column[i]] = player;
				slots[column[i]].classList.add(player);
				switchPlayer(player);
				break;
			}	
		}

		function switchPlayer(currentPlayer) {
			if(currentPlayer === 'p1') player = 'p2';
			else if(currentPlayer ==='p2') player = 'p1';
		}
	}
}
/** {
	outline: 1px solid red;
}*/

*, *:before, *:after {
	box-sizing: inherit;
}

html {
	box-sizing: border-box;
}

html, body {
	margin: 0;
	padding: 0;
	background-color: #e5e6e8;
}

body {
	display: flex;
	justify-content: center;
	min-height: 100vh;
}

.board-wrapper {
	padding-top: 100px;
	display: flex;
	justify-content: center;
	margin: auto auto 0 auto; /*ask why this is needed*/
	position: relative;
	overflow: hidden;
}

.board {
	display: flex;
	flex-wrap: wrap;
	max-width: 706px;
	background-color: #00c;
	padding: 3px;
}

.board div {
	width: 100px;
	height: 100px;
	background-color: blue;
	border: 3px solid #00c;
	position: relative;
	z-index: 3;
}

.board div span {
	display: inline-block;
	width: 80px;
	height: 80px;
	border-radius: 50%;
	background-color: #00c;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	margin: auto;
	box-shadow: inset 0px 0px 13px #0606aa;
}

.board .chip {
	display: block;
	position: absolute;
	background-color: transparent;
	top: 0;
	left: 0;
	right: 0;
	height: 100px;
}

.board .chip:after {
	content: "";
	width: 80px;
	height: 80px;
	border-radius: 50%;
	background-color: red;
	position: absolute;
	left: 3px;
	top: 0;
	opacity: 0;
	transition: all .5s ease;
}

.board .chip:before {
	content: "";
	width: 50px;
	height: 50px;
	border-radius: 50%;
	background-color: red;
	position: absolute;
	left: 18px;
	top: 15px;
	z-index: 1;
	box-shadow: inset 0px 0px 20px #cc0000;
	opacity: 0;
	transition: all .5s ease;	
}

.board div:nth-of-type(7n+1):hover ~ .chip:after{transform: translateX(10px); opacity: 1;}
.board div:nth-of-type(7n+1):hover ~ .chip:before{transform: translateX(10px); opacity: 1;}
.board div:nth-of-type(7n+2):hover ~ .chip:after{transform: translateX(110px); opacity: 1;}
.board div:nth-of-type(7n+2):hover ~ .chip:before{transform: translateX(110px); opacity: 1;}
.board div:nth-of-type(7n+3):hover ~ .chip:after{transform: translateX(210px); opacity: 1;}
.board div:nth-of-type(7n+3):hover ~ .chip:before{transform: translateX(210px); opacity: 1;}
.board div:nth-of-type(7n+4):hover ~ .chip:after{transform: translateX(310px); opacity: 1;}
.board div:nth-of-type(7n+4):hover ~ .chip:before{transform: translateX(310px); opacity: 1;}
.board div:nth-of-type(7n+5):hover ~ .chip:after{transform: translateX(410px); opacity: 1;}
.board div:nth-of-type(7n+5):hover ~ .chip:before{transform: translateX(410px); opacity: 1;}
.board div:nth-of-type(7n+6):hover ~ .chip:after{transform: translateX(510px); opacity: 1;}
.board div:nth-of-type(7n+6):hover ~ .chip:before{transform: translateX(510px); opacity: 1;}
.board div:nth-of-type(7n+7):hover ~ .chip:after{transform: translateX(610px); opacity: 1;}
.board div:nth-of-type(7n+7):hover ~ .chip:before{transform: translateX(610px); opacity: 1;}

.p1:after {
	content: "";
	display: inline-block;
	width: 80px;
	height: 80px;
	border-radius: 50%;
	background-color: red;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	margin: auto;
	z-index: 1;
	animation-name: drop;
	animation-fill-mode: forwards;
	animation-duration: .5s;
	animation-timing-function: ease-in;
}

.p1:before {
	content: "";
	width: 50px;
	height: 50px;
	border-radius: 50%;
	background-color: red;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	margin: auto;
	z-index: 2;
	box-shadow: inset 0px 0px 20px #cc0000;
	animation-name: drop;
	animation-fill-mode: forwards;
	animation-duration: .5s;
	animation-timing-function: ease-in;
}

.p2:after {
	content: "";
	display: inline-block;
	width: 80px;
	height: 80px;
	border-radius: 50%;
	background-color: yellow;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	margin: auto;
	z-index: 1;
	animation-name: drop;
	animation-fill-mode: forwards;
	animation-duration: .5s;
	animation-timing-function: ease-in;
}

.p2:before {
	content: "";
	width: 50px;
	height: 50px;
	border-radius: 50%;
	background-color: yellow;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	margin: auto;
	z-index: 2;
	box-shadow: inset 0px 0px 20px #ced639;
	animation-name: drop;
	animation-fill-mode: forwards;
	animation-duration: .5s;
	animation-timing-function: ease-in;
}

@keyframes drop {
	from {top: -1500px;}
	to {top: 0;}
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Connect Four</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div class="board-wrapper">
		<div class="board">
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<div></div>
			<span class="chip"></span>
		</div>
	</div>
	<script src="script.js"></script>
</body>
</html>


Solution

  • Here is a modified version of your code. First I have changed the chip element to consider only one pseudo element instead of 2 and I used a CSS variable in order to easily change the color.

    Then for the board I created each cell using two elements in order to be able to have the 3D effect. You will see the pseudo element where I applied a radial gradient in order to create a hole and this layer will be on the top this the chip will drop behind:

    //grab all slot positions on the board
    const slots = document.querySelectorAll('.board div');
    let player = 'p1';
    let board = [
      0, 1, 2, 3, 4, 5, 6,
      7, 8, 9, 10, 11, 12, 13,
      14, 15, 16, 17, 18, 19, 20,
      21, 22, 23, 24, 25, 26, 27,
      28, 29, 30, 31, 32, 33, 34,
      35, 36, 37, 38, 39, 40, 41,
    ]
    
    //assign a class to each slot to represent its position
    for (let i = 0; i < slots.length; i++) {
      //add class to each div
      slots[i].classList.add('c' + i);
      //add the slot to each div
      let slot = document.createElement('span');
      slots[i].appendChild(slot);
      //add the function with the game logic to each slot
      slots[i].addEventListener('click', runGame);
    }
    
    function runGame() {
      //figure out which column the selected slot sits in
      const slotColumn = (Number(this.className.slice(1, 3)) % 7);
      //create an array to store all the slots that share the above column
      const columnArray = [];
    
      //grab all the slots that sit in that column
      for (let i = 0; i < board.length; i++) {
        if (board[i] % 7 === slotColumn) columnArray.push(board[i]);
      }
    
      //drop chip in the chosen column
      dropChip(columnArray);
    
      function dropChip(column) {
        //select bottom most slot that's available in the column
        for (let i = column.length - 1; i >= 0; i--) {
          if (column[i] !== 'p1' || column[i] !== 'p2') {
            board[column[i]] = player;
            slots[column[i]].classList.add(player);
            switchPlayer(player);
            break;
          }
        }
    
        function switchPlayer(currentPlayer) {
          if (currentPlayer === 'p1') player = 'p2';
          else if (currentPlayer === 'p2') player = 'p1';
        }
      }
    }
    /** {
    	outline: 1px solid red;
    }*/
    
    *,
    *:before,
    *:after {
      box-sizing: inherit;
    }
    
    html {
      box-sizing: border-box;
    }
    
    html,
    body {
      margin: 0;
      padding: 0;
      background-color: #e5e6e8;
    }
    
    body {
      display: flex;
      justify-content: center;
      min-height: 100vh;
    }
    
    .board-wrapper {
      padding-top: 100px;
      display: flex;
      justify-content: center;
      margin: auto auto 0 auto; /*ask why this is needed*/
      position: relative;
      overflow: hidden;
    }
    
    .board {
      display: flex;
      flex-wrap: wrap;
      max-width: 706px;
      background-color: #00c;
      padding: 3px;
    }
    
    .board div {
      width: 100px;
      height: 100px;
      position: relative;
    }
    
    .board div span {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      background-color: #00c;
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      margin: auto;
      box-shadow: inset 0px 0px 13px #0606aa;
    }
    
    .board div span:before {
      content: "";
      position: absolute;
      top: -10px;
      left: -10px;
      right: -10px;
      bottom: -10px;
      background: radial-gradient(circle, transparent 40px, blue 0);
      border: 3px solid #00c;
      z-index: 3;
    }
    
    .board .chip {
      display: block;
      position: absolute;
      background-color: transparent;
      top: 0;
      left: 0;
      right: 0;
      height: 100px;
    }
    
    .board .chip:after {
      content: "";
      width: 80px;
      height: 80px;
      border-radius: 50%;
      border: 15px solid red;
      background-color: red;
      box-shadow: inset 0px 0px 20px #cc0000;
      position: absolute;
      left: 3px;
      top: 0;
      opacity: 0;
      transition: all .5s ease;
    }
    
    .board div:nth-of-type(7n+1):hover~.chip:after {
      transform: translateX(10px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+1):hover~.chip:before {
      transform: translateX(10px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+2):hover~.chip:after {
      transform: translateX(110px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+2):hover~.chip:before {
      transform: translateX(110px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+3):hover~.chip:after {
      transform: translateX(210px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+3):hover~.chip:before {
      transform: translateX(210px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+4):hover~.chip:after {
      transform: translateX(310px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+4):hover~.chip:before {
      transform: translateX(310px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+5):hover~.chip:after {
      transform: translateX(410px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+5):hover~.chip:before {
      transform: translateX(410px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+6):hover~.chip:after {
      transform: translateX(510px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+6):hover~.chip:before {
      transform: translateX(510px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+7):hover~.chip:after {
      transform: translateX(610px);
      opacity: 1;
    }
    
    .board div:nth-of-type(7n+7):hover~.chip:before {
      transform: translateX(610px);
      opacity: 1;
    }
    
    .p1:after,
    .p2:after {
      content: "";
      display: inline-block;
      width: 80px;
      height: 80px;
      border-radius: 50%;
      border: 15px solid var(--c, red);
      background-color: var(--c, red);
      box-shadow: inset 0px 0px 20px var(--s, #cc0000);
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      margin: auto;
      z-index: 1;
      animation-name: drop;
      animation-fill-mode: forwards;
      animation-duration: .5s;
      animation-timing-function: ease-in;
    }
    
    .p2 {
      --c: yellow;
      --s: #ced639;
    }
    
    @keyframes drop {
      from {
        top: -1500px;
      }
      to {
        top: 0;
      }
    }
    <div class="board-wrapper">
      <div class="board">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <span class="chip"></span>
      </div>
    </div>