Build a Custom Full Page Slider with CSS and JavaScript

Build a Custom Full Page Slider with CSS and JavaScript

Build a Custom Full Page Slider with CSS and JavaScript - This tutorial demonstrates how to build a full page slider using nothing but CSS and JavaScript, with no plugins or bloated JS libraries...

I work with custom full-screen layouts a lot, practically on a daily basis. Usually, these layouts imply a substantial amount of interaction and animation. Whether it be a time-triggered complex timeline of transitions or a scroll-based user-driven set of events, in most cases, the UI requires more than just using an out-of-the-box plugin solution with a few tweaks and changes. On the other hand, I see many JavaScript developers tend to reach for their favorite JS plugin to make their job easier, even though the task may not need all the bells and whistles a certain plugin provides.

Disclaimer: Using one of the many plugins available out there has its perks, of course. You’ll get a variety of options you can use to tweak to your needs without having to do much coding. Also, most plugin authors optimize their code, make it cross-browser and cross-platform compatible, and so on. But still, you get a full-size library included in your project for maybe only one or two different things it provides. I’m not saying using a third-party plugin of any kind is naturally a bad thing, I do it on a daily basis in my projects, just that it is generally a good idea to weigh pros and cons of each approach as it is a good practice in coding. When it comes to doing your own thing this way, it requires a bit more coding knowledge and experience to know what you are looking for, but in the end, you should get a piece of code that does one thing and one thing only the way you want it to.

This article is aimed to show a pure CSS/JS approach in developing a fullscreen scroll-triggered slider layout with custom content animation. In this scaled-down approach, I’ll cover the basic HTML structure you would expect to be delivered from a CMS back-end, modern CSS (SCSS) layout techniques, and vanilla JavaScript coding for full interactivity. Being bare-bones, this concept can be easily extended to a larger-scale plugin and/or used in a variety of applications having no dependencies at its core.

The design we are going to create is a minimalistic architect portfolio showcase with featured images and titles of each project. The complete slider with animations will look like this:

You can check out the demo here, and you can access my Github repo for further details.

HTML Overview

So here’s the basic HTML we will be working with:


A div with the id of hero-slider is our main holder. Inside, the layout is divided into sections:

  • Logo (a static section)
  • Slideshow which we’ll work on mostly
  • Info (a static section)
  • Slider nav which will indicate the currently active slide as well as the total number of slides

Let’s focus on the slideshow section since that is our point of interest in this article. Here we have two parts— main and aux. Main is the div which contains featured images while aux holds image titles. The structure of each slide inside of these two holders is pretty basic. Here we have an image slide inside of the main holder:



The index data attribute is what we’ll use to keep track of where we are at in the slideshow. The abs-mask div we’ll use to create an interesting transition effect and the slide-image div contains the specific featured image. Images are rendered inline as if they were coming directly from a CMS and are set by the end user.

Similarly, the title slides inside of the aux holder:

## [#64 Paradigm](# "#64 Paradigm")

Each slide title is an H2 tag with the corresponding data attribute and a link to be able to lead to that project’s single page.

The rest of our HTML is pretty straightforward as well. We have a logo at the top, static info which tells the user which page they are on, some description, and slider current/total indicator.

CSS Overview

The source CSS code is written in SCSS, a CSS pre-processor which is then compiled into regular CSS which the browser can interpret. SCSS gives you the advantage of using variables, nested selection, mixins, and other cool stuff, but it needs to be compiled into CSS to have the browser read the code as it should. For the purpose of this tutorial, I’ve used Scout-App to handle the compiling as I’ve wanted to have the tooling at the bare minimum.

I used flexbox to handle the basic side-by-side layout. The idea is to have the slideshow on one side and the info section on the other.

#hero-slider {
	position: relative;
	height: 100vh;
	display: flex;
	background: $dark-color;

#slideshow {
	position: relative;
	flex: 1 1 $main-width;
	display: flex;
	align-items: flex-end;
	padding: $offset;

#info {
	position: relative;
	flex: 1 1 $side-width;
	padding: $offset;
	background-color: #fff;

Let’s dive into the positioning and again, focus on the slideshow section:

#slideshow {
	position: relative;
	flex: 1 1 $main-width;
	display: flex;
	align-items: flex-end;
	padding: $offset;

#slides-main {
	@extend %abs;

	&:after {
		content: '';
		@extend %abs;
		background-color: rgba(0, 0, 0, .25);
		z-index: 100;

	.slide-image {
		@extend %abs;
		background-position: center;
		background-size: cover;
		z-index: -1;

#slides-aux {
	position: relative;
	top: 1.25rem;
	width: 100%;

	.slide-title {
		position: absolute;
		z-index: 300;
		font-size: 4vw;
		font-weight: 700;
		line-height: 1.3;
		@include outlined(#fff);

I’ve set the main slider to be absolutely positioned and have the background images stretch the whole area by using the background-size: cover property. To provide more contrast against the slide titles, I’ve set an absolute pseudo-element which acts as an overlay. The aux slider containing slide titles is positioned at the bottom of the screen and on top of the images.

Since only one slide will be visible at a time, I set each title to be absolute as well, and have the holder size calculated via JS to make sure there are no cut-offs, but more about that in one of our upcoming sections. Here you can see the use of an SCSS feature called extending:

%abs {
	position: absolute;
	top: 0;
	left: 0;
	height: 100%;
	width: 100%;

Since I used absolute positioning a lot, I pulled this CSS into an extendable to have it easily available in various selectors. Also, I created a mixin called “outlined” to provide a DRY approach when styling the titles and main slider title.

@mixin outlined($color: $dark-color, $size: 1px) {
	color: transparent;
	-webkit-text-stroke: $size $color;

As for the static part of this layout, it has nothing complex about it but here you can see an interesting method when positioning text which has to be on the Y axis instead of its normal flow:

.slider-title-wrapper {
	position: absolute;
	top: $offset;    
	left: calc(100% - #{$offset});
    transform-origin: 0% 0%;
    transform: rotate(90deg);
    @include outlined;

I’d like to draw your attention to the transform-origin property as I found it really underused for this type of layout. The way this element is positioned is that its anchor stays in the upper left corner of the element, setting the rotation point and having the text continuously flow from that point downwards with no issues when it comes to different screen sizes.

Let’s have a look at a more interesting CSS part - the initial loading animation:

Usually, this kind of synced animation behavior is achieved using a library - GSAP, for example, is one of the best out there, providing excellent rendering capabilities, is easy to use, and has the timeline functionality which enables the developer to programmatically chain element transitions into each other.

However, as this is a pure CSS/JS example I’ve decided to go really basic here. So each element is set to its starting position by default–either hidden by transform or opacity and shown upon slider load which is triggered by our JS. All transition properties are manually tweaked to ensure a natural and interesting flow with each transition continuing into another providing a pleasant visual experience.

#logo:after {
	transform: scaleY(0);
	transform-origin: 50% 0;
	transition: transform .35s $easing;

.logo-text {
	display: block;
	transform: translate3d(120%, 0, 0);
    opacity: 0;
    transition: transform .8s .2s, opacity .5s .2s;
.sep:before {
	opacity: 0;
    transition: opacity .4s 1.3s;

#info {
	transform: translate3d(100%, 0, 0);
    transition: transform 1s $easing .6s;

.line {
	transform-origin: 0% 0;
    transform: scaleX(0);
    transition: transform .7s $easing 1s;

.slider-title {
	overflow: hidden;

	>span {
	display: block;
    transform: translate3d(0, -100%, 0);
    transition: transform .5s 1.5s;

If there’s one thing I would like you to see here, it is the use of the transform property. When moving an HTML element, whether it be a transition or animation, it is advised to use the transform property. I see a lot of people who tend to ruse either margin or padding or even the offsets–top, left, etc. which doesn’t produce adequate results when it comes to rendering.

To gain a more in-depth grasp of how to use CSS when adding interactive behavior, I couldn’t recommend the following article enough.

It’s by Paul Lewis, a Chrome engineer, and covers pretty much everything one should know about pixel rendering in web whether it be CSS or JS.

JavaScript Overview and Slider Logic

The JavaScript file is divided into two distinct functions.

The heroSlider function which takes care of all the functionality we need here, and the utilsfunction where I’ve added several reusable utility functions. I’ve commented each of these utility functions to provide context if you are looking to reuse them in your project.

const slider = {
       hero: document.querySelector('#hero-slider'),
       main: document.querySelector('#slides-main'),
       aux: document.querySelector('#slides-aux'),
       current: document.querySelector('#slider-nav .current'),
       handle: null,
       idle: true,
       activeIndex: -1,
       interval: 3500

As a side-note, this approach could be easily adapted if you’re for example using React, as you can store the data in state or use the newly added hooks. To stay on point, let’s just go through what each of the key-value pairs here represents:

  • Logo (a static section)
  • Slideshow which we’ll work on mostly
  • Info (a static section)
  • Slider nav which will indicate the currently active slide as well as the total number of slides

Upon slider initialization, we invoke two functions:

setHeight(slider.aux, slider.aux.querySelectorAll('.slide-title'));

The setHeight function reaches out to a utility function to set the height of our aux slider based on the maximum title size. This way we ensure that adequate sizing is provided and no slide title will be cut off even when its content drops into two lines.

loadingAnimation function adds a CSS class to the element providing the intro CSS transitions:

const loadingAnimation = function () {
    slider.current.addEventListener('transitionend', start, {
        once: true

As our slider indicator is the last element in the CSS transition timeline, we wait for its transition to end and invoke the start function. By providing additional parameter as an object we ensure that this is triggered only once.

Let’s have a look at the start function:

const start = function () {
	window.innerWidth <= 1024 && touchControl();
	slider.aux.addEventListener('transitionend', loaded, {
		once: true

So when the layout has finished, its initial transition is triggered by loadingAnimation function and the start function takes over. It then triggers autoplay functionality, enables wheel control, determines if we are on a touch or desktop device, and waits for the titles slide first transition to add the appropriate CSS class.


One of the core features in this layout is the autoplay feature. Let’s go over the corresponding function:

const autoplay = function (initial) {
	slider.autoplay = true;
	slider.items = slider.hero.querySelectorAll('[data-index]'); = slider.items.length / 2;

	const loop = () => changeSlide('next');

	initial && requestAnimationFrame(loop);
	slider.handle = utils().requestInterval(loop, slider.interval);

First, we set the autoplay flag to true, indicating the slider is in autoplay mode. This flag is useful when determining whether to re-trigger autoplay after the user interacts with the slider. We then reference all slider items (slides), as we’ll change their active class and calculate the total iterations the slider is going to have by adding up all items and dividing by two as we have two synced slider layouts (main and aux) but only one “slider” per se which changes both of them simultaneously.

The most interesting part of the code here is the loop function. It invokes slideChange, providing the slide direction which we’ll go over in a minute, however, the loop function is called a couple of times. Let’s see why.

If the initial argument is evaluated as true, we’ll invoke the loop function as a [requestAnimationFrame]( "requestAnimationFrame") callback. This is only happening upon the first slider load which triggers immediate slide change. Using requestAnimationFrame we execute the provided callback just before the next frame repaint.

The main function is coded in a way that it has two branches: init and resize. These branches are available via return of the main function and are invoked when necessary. init is the initialization of the main function and it’s triggered on window load event. Similarly, the resize branch is triggered on window resize. The sole purpose of the resize function is to recalculate the title’s slider size on window resize, as title font size may vary.

In the heroSlider function, I’ve provided a slider object which contains all the data and selectors we are going to need:

However, as we want to keep going through slides in autoplay mode we’ll use a repeated call of this same function. This is usually achieved with setInterval. But in this case, we’ll use one of the utility functions–requestInterval. While setInterval would work just well, requestInterval is an advanced concept which relies on requestAnimationFrame and provides a more performant approach. It ensures that function is re-triggered only if the browser tab is active.

More about this concept in this awesome article can be found on CSS tricks. Please note that we assign the return value from this function to our slider.handle property. This unique ID the function returns is available to us and we’ll use it to cancel autoplay later on using [cancelAnimationFrame]( "cancelAnimationFrame").

Slide Change

The slideChange function is the principal function in the whole concept. It changes slides whether it’d by autoplay or by user trigger. It is aware of slider direction, provides looping so when you come to the last slide you’ll be able to continue to the first slide. Here’s how I’ve coded it:

const changeSlide = function (direction) {
	slider.idle = false;
	slider.hero.classList.remove('prev', 'next');
	if (direction == 'next') {
		slider.activeIndex = (slider.activeIndex + 1) %;
	} else {
		slider.activeIndex = (slider.activeIndex - 1 + %;

	//reset classes
	utils().removeClasses(slider.items, ['prev', 'active']);

	//set prev 
	const prevItems = [...slider.items]
		.filter(item => {
			let prevIndex;
			if (slider.hero.classList.contains('prev')) {
				prevIndex = slider.activeIndex == - 1 ? 0 : slider.activeIndex + 1;
           } else {
               prevIndex = slider.activeIndex == 0 ? - 1 : slider.activeIndex - 1;

           return item.dataset.index == prevIndex;

   //set active
	const activeItems = [...slider.items]
       .filter(item => {
           return item.dataset.index == slider.activeIndex;

	utils().addClasses(prevItems, ['prev']);
	utils().addClasses(activeItems, ['active']);

	const activeImageItem = slider.main.querySelector('.active');
	activeImageItem.addEventListener('transitionend', waitForIdle, {
		once: true

The idea is to determine the active slide based on its data-index we got from HTML. Let’s address each step:

  1. Set slider idle flag to false. This indicates that slide change is in progress and wheel and touch gestures are disabled.
  2. The previous slider direction CSS class gets reset and we check for the new one. The direction parameter is provided either by default as ‘next’ if we are coming from the autoplay function or by a user invoked function–wheelControl or touchControl.
  3. Based on the direction, we calculate the active slide index and provide the current direction CSS class to the slider. This CSS class is used to determine which transition effect will be used (e.g. right to left or left to right)
  4. Slides get their “state” CSS classes (prev, active) reset using another utility function which removes CSS classes but can be invoked on a NodeList, rather than just a single DOM element. Afterward, only previous and currently active slides get those CSS classes added to them. This allows for the CSS to target only those slides and provide adequate transitioning.
  5. setCurrent is a callback which updates slider indicator based on the activeIndex.
  6. Finally, we wait for the transition of the active image slide to end in order to trigger the waitForIdle callback which restarts autoplay if it was previously interrupted by the user.
User Controls

Based on the screen size, I’ve added two types of user controls–wheel and touch. Wheel control:

const wheelControl = function () {
	slider.hero.addEventListener('wheel', e => {
       if (slider.idle) {
           const direction = e.deltaY > 0 ? 'next' : 'prev';

Here, we listen to wheel even and if the slider is currently in idle mode (not currently animating a slide change) we determine wheel direction, invoke stopAutoplay to stop the autoplaying function if it’s in progress, and change the slide based on the direction. The stopAutoplay function is nothing but a simple function that sets our autoplay flag to the false value and cancels our interval by invoking cancelRequestInterval utility function passing it the appropriate handle:

const stopAutoplay = function () {
	slider.autoplay = false;

Similar to wheelControl, we have touchControl that takes care of touch gestures:

const touchControl = function () {
	const touchStart = function (e) {
       slider.ts = parseInt(e.changedTouches[0].clientX);
       window.scrollTop = 0;

   const touchMove = function (e) { = parseInt(e.changedTouches[0].clientX);
       const delta = - slider.ts;
       window.scrollTop = 0;

       if (slider.idle) {
           const direction = delta < 0 ? 'next' : 'prev';

	slider.hero.addEventListener('touchstart', touchStart);
	slider.hero.addEventListener('touchmove', touchMove);

We listen for two events: touchstart and touchmove. Then, we calculate the difference. If it returns a negative value, we change to the next slide as the user has swiped from right to left. On the other hand, if the value is positive, meaning that the user has swiped from left to right, we trigger slideChange with direction passed as “previous.” In both cases, autoplay functionality gets stopped.

This is a pretty simple user gesture implementation. To build on this, we could add previous/next buttons to trigger slideChange on click or add a bulleted list to go directly to a slide based on its index.

Wrap-up and Final Thoughts on CSS

So there you go, a pure CSS/JS way of coding a non-standard slider layout with modern transition effects.

I hope you find this approach useful as a way of thinking and could use something similar in your front-end projects when coding for a project that was not necessarily conventionally designed.

For those of you interested in the image transition effect, I’ll go over this in the next few lines.

If we revisit the slides HTML structure I’ve provided in the intro section we’ll see that each image slide has a div around it with the CSS class of abs-mask. What this div does is that it hides a portion of the visible image by a certain amount by using overflow:hidden and offsetting it in a different direction than the image. For example, if we look at the way the previous slide is coded:

&.prev {
	z-index: 5;
	transform: translate3d(-100%, 0, 0);
	transition: 1s $easing;

	.abs-mask {
       transform: translateX(80%);
       transition: 1s $easing;

The previous slide has a -100% offset in its X axis, moving it to the left of the current slide, however, the inner abs-mask div is translated 80% to the right, providing a narrower viewport. This, in combination with having a larger z-index for the active slide results in a sort of cover effect—the active image covers the previous while in the same time extending its visible area by moving the mask which provides the full view.

How to Create Attendance Register in JavaScript, CSS, and HTML

How to Create Attendance Register in JavaScript, CSS, and HTML

How to Create Attendance Register in JavaScript, CSS, and HTML. The tutorial includes the use of JavaScript functions, image, Table, Option, input, and other components.

How to Create Attendance Register in JavaScript, CSS, and HTML. The tutorial includes the use of JavaScript functions, image, Table, Option, input, and other components.

Building a minesweeper game using Javascript, HTML and CSS

Building a minesweeper game using Javascript, HTML and CSS

In this article, you'll learn how to build minesweeper using JavaScript, HTML and CSS. I also used jQuery, a JavaScript library that is helpful for interacting with html. Whenever you see a function call with a leading dollar sign, that is jQuery at work

In this article, you'll learn how to build minesweeper using JavaScript, HTML and CSS. I also used jQuery, a JavaScript library that is helpful for interacting with html. Whenever you see a function call with a leading dollar sign, that is jQuery at work

If you want to learn how to build minesweeper with JavaScript, the first step is understanding how the game works. Let’s jump right in and talk about the rules.

Rules of the Game
  1. The minesweeper board is a 10 x 10 square. We could make it other sizes, like the classic Windows version, but for demonstration purposes we will stick to the smaller, “beginner” version of the game.
  2. The board has a predetermined number of randomly placed mines. The player cannot see them.
  3. Cells can exist in one of two states: opened or closed. Clicking on a cell opens it. If a mine was lurking there, the game ends in failure. If there is no mine in the cell, but there are mines in one or more of its neighboring cells, then the opened cell shows the neighboring mine count. When none of the cell’s neighbors are mined, each one of those cells is opened automatically.
  4. Right clicking on a cell marks it with a flag. The flag indicates that the player knows there is a mine lurking there.
  5. Holding down the ctrl button while clicking on an opened cell has some slightly complicated rules. If the number of flags surrounding the cell match its neighbor mine count, and each flagged cell actually contains a mine, then all closed, unflagged neighboring cells are opened automatically. However, if even one of these flags was placed on the wrong cell, the game ends in failure.
  6. The player wins the game if he/she opens all cells without mines.
Data Structures


JavaScript code representing a minesweeper cell.

Each cell is an object that has several properties:

  • id: A string containing the row and column. This unique identifier makes it easier to find cells quickly when needed. If you pay close attention you will notice that there are some shortcuts I take related to the ids. I can get away with these shortcuts because of the small board size, but these techniques will not scale to larger boards. See if you can spot them. If you do, point them out in the comments!
  • row: An integer representing the horizontal position of the cell within the board.
  • column: An integer representing the vertical position of the cell within the board.
  • opened: This is a boolean property indicating whether the cell has been opened.
  • flagged: Another boolean property indicating whether a flag has been placed on the cell.
  • mined: Yet another boolean property indicating whether the cell has been mined.
  • neighborMineCount: An integer indicating the number of neighboring cells containing a mine.


JavaScript code representing our game board.

Our board is a collection of cells. We could represent our board in many different ways. I chose to represent it as an object with key value pairs. As we saw earlier, each cell has an id. The board is just a mapping between these unique keys and their corresponding cells.

After creating the board we have to do two more tasks: randomly assign the mines and calculate the neighboring mine counts. We’ll talk more about these tasks in the next section.


Randomly Assign Mines

JavaScript code for randomly assigning mines to cells.

One of the first things we have to do before a game of minesweeper can be played is assign mines to cells. For this, I created a function that takes the board and the desired mine count as parameters.

For every mine we place, we must generate a random row and column. Furthermore, the same row and column combination should never appear more than once. Otherwise we would end up with less than our desired number of mines. We must repeat the random number generation if a duplicate appears.

As each random cell coordinate is generated we set the *mined *property to true of the corresponding cell in our board.

I created a helper function in order to help with the task of generating random numbers within our desired range. See below:

Helper function for generating random integers.

Calculate Neighbor Mine Count

JavaScript code for calculating the neighboring mine count of each cell.

Now let’s look at what it takes to calculate the neighboring mine count of each cell in our board.

You’ll notice that we start by looping through each row and column on the board, a very common pattern. This will allow us to execute the same code on each of our cells.

We first check if each cell is mined. If it is, there is no need to check the neighboring mine count. After all, if the player clicks on it he/she will lose the game!

If the cell is not mined then we need to see how many mines are surrounding it. The first thing we do is call our **getNeighbors **helper function, which returns a list of ids of the neighboring cells. Then we loop through this list, add up the number of mines, and update the cell’s *neighborMineCount *property appropriately.

Won’t you be my neighbor?

Let’s take a closer look at that **getNeighbors **function, as it will be used several more times throughout the code. I mentioned earlier that some of my design choices won’t scale to larger board sizes. Now would be a good time to try and spot them.

JavaScript code for getting all of the neighboring ids of a minesweeper cell.

The function takes a cell id as a parameter. Then we immediately split it into two pieces so that we have variables for the row and the column. We use the **parseInt **function, which is built into the JavaScript language, to turn these variables into integers. Now we can perform math operations on them.

Next, we use the row and column to calculate potential ids of each neighboring cell and push them onto a list. Our list should have eight ids in it before cleaning it up to handle special scenarios.

A minesweeper cell and its neighbors.

While this is fine for the general case, there are some special cases we have to worry about. Namely, cells along the borders of our game board. These cells will have less than eight neighbors.

In order to take care of this, we loop through our list of neighbor ids and remove any id that is greater than 2 in length. All invalid neighbors will either be -1 or 10, so this little check solves the problem nicely.

We also have to decrement our index variable whenever we remove an id from our list in order to keep it in sync.

Is it mined?

Okay, we have one last function to talk about in this section: isMined.

JavaScript function that checks if a cell is mined.

The **isMined **function is pretty simple. It just checks if the cell is mined or not. The function returns a 1 if it is mined, and a 0 if it is not mined. This feature allows us to sum up the function’s return values as we call it repeatedly in the loop.

That wraps up the algorithms for getting our minesweeper game board set up. Let’s move on to the actual game play.

Opening A Cell

JavaScript code that executes when a minesweeper cell is opened.

Alright let’s dive right into this bad boy. We execute this function whenever a player clicks on a cell. It does a lot of work, and it also uses something called recursion. If you are unfamiliar with the concept, see the definition below:

Recursion: See recursion.

Ah, computer science jokes. They always go over so well at bars and coffee shops. You really ought to try them out on that cutie you’ve been crushing on.

Anyways, a recursive function is just a function that calls itself. Sounds like a stack overflow waiting to happen, right? That’s why you need a base case that returns a value without making any subsequent recursive calls.

Our function will eventually stop calling itself because there will be no more cells that need to be opened. We could have written this code without recursion, but I thought you all might want to see an example of it in action.

Handle Click Explained

The **handleClick **function takes a cell id as a parameter. We need to handle the case where the player pressed the ctrl button while clicking on the cell, but we will talk about that in a later section.

Assuming the game isn’t over and we are handling a basic left click event, there are a few checks we need to make. We want to ignore the click if the player already opened or flagged the cell. It would be frustrating for the player if an inaccurate click on an already flagged cell ended the game.

If neither of those are true then we will proceed. If a mine is present in the cell we need to initiate the game over logic and display the exploded mine in red. Otherwise, we will open the cell.

If the opened cell has mines surrounding it we will display the neighboring mine count to the player in the appropriate font color. If there are no mines surrounding the cell, then it is time for our recursion to kick in. After setting the background color of the cell to a slightly darker shade of gray, we call **handleClick **on each unopened neighboring cell without a flag.

Helper Functions

Let’s take a look at the helper functions we are using inside the **handleClick **function. We’ve already talked about getNeighbors, so we’ll skip that one. Let’s start with the **loss **function.

JavaScript code that gets called whenever the player has lost the game.

When a loss occurs, we set the variable that tracks this and then display a message letting the player know that the game is over. We also loop through each cell and display the mine locations. Then we stop the clock.

Second, we have the **getNumberColor **function. This function is responsible for giving us the color corresponding to the neighboring mine count.

JavaScript code that gets passed a number and returns a color.

I tried to match up the colors just like the classic Windows version of minesweeper does it. Maybe I should have used a switch statement, but I already took the screen shot, and it’s not really a big deal. Let’s move on to what the code looks like for putting a flag on a cell.

Flagging A Cell

JavaScript code for putting a flag on a minesweeper cell.

Right clicking on a cell will place a flag on it. If the player right clicks on an empty cell and we have more mines that need to be flagged we will display the red flag on the cell, update its flagged property to true, and decrement the number of mines remaining. We do the opposite if the cell already had a flag. Finally, we update the GUI to display the number of mines remaining.

Opening Neighboring Cells

JavaScript code for handling ctrl + left click

We have covered the actions of opening cells and marking them with flags, so let’s talk about the last action a player can take: opening an already opened cell’s neighboring cells. The **handleCtrlClick **function contains the logic for this. This player can perform this action by holding ctrl and left clicking on an opened cell that contains neighboring mines.

The first thing we do after checking those conditions is build up a list of the neighboring flagged cells. If the number of flagged cells matches the actual number of surrounding mines then we can proceed. Otherwise, we do nothing and exit the function.

If we were able to proceed, the next thing we do is check if any of the flagged cells did not contain a mine. If this is true, we know that the player predicted the mine locations incorrectly, and clicking on all of the non-flagged, neighboring cells will end in a loss. We will need to set the local *lost *variable and call the **loss **function. We talked about the **loss **function earlier in the article.

If the player did not lose, then we will need to open up the non-flagged neighboring cells. We simply need to loop through them and call the **handleClick **function on each. However, we must first set the *ctrlIsPressed *variable to false to prevent falling into the **handleCtrlClick **function by mistake.

Starting A New Game

We are almost done analyzing all of the JavaScript necessary to build minesweeper! All that we have left to cover are the initialization steps necessary for starting a new game.

JavaScript code for initializing minesweeper

The first thing we do is initialize a few variables. We need some constants for storing the html codes for the flag and mine icons. We also need some constants for storing the board size, the number of mines, the timer value, and the number of mines remaining.

Additionally, we need a variable for storing if the player is pushing the ctrl button. We utilize jQuery to add the event handlers to the document, and these handlers are responsible for setting the *ctrlIsPressed *variable.

Finally, we call the **newGame **function and also bind this function to the new game button.

Helper Functions

JavaScript code for starting a new game of minesweeper.

Th **newGame **function is responsible for resetting our variables so that our game is in a ready-to-play state. This includes resetting the values that are displayed to the player, calling initializeCells, and creating a new random board. It also includes resetting the clock, which gets updated every second.

Let’s wrap things up by looking at initializeCells.

JavaScript code for attaching click handlers to cells and checking for the victory condition.

The main purpose of this function is to add additional properties to our html game cells. Each cell needs the appropriate id added so that we can access it easily from the game logic. Every cell also needs a background image applied for stylistic reasons.

We also need to attach a click handler to every cell so that we can detect left and right clicks.

The function that handles left clicks calls handleClick, passing in the appropriate id. Then it checks to see if every cell without a mine has been opened. If this is true then the player has won the game and we can congratulate him/her appropriately.

The function that handles right clicks calls handleRightClick, passing in the appropriate id. Then it simply returns false. This causes the context menu not to pop up, which is the default behavior of a right click on a web page. You wouldn’t want to do this sort of thing for a standard business CRUD application, but for minesweeper it is appropriate.


Congrats on learning how to build minesweeper with JavaScript! That was a lot of code, but hopefully it makes sense after breaking it up into modules like this. We could definitely make more improvements to this program’s reusability, extensibility, and readability. We also did not cover the HTML or CSS in detail. If you have questions or see ways to improve the code, I’d love to hear from you in the comments!

Build a Quiz App with HTML, CSS, and JavaScript

<strong>Build a Quiz App with HTML, CSS, and JavaScript to improve your Core Web Development skills!</strong>

Build a Quiz App with HTML, CSS, and JavaScript to improve your Core Web Development skills!

In this video, we will create a simple spinning loader in CSS that will be displayed until we are finished requesting/loading questions from the API.

Course Content:

Learn More

The Web Developer Bootcamp

The Complete 2019 Web Development Bootcamp

The Complete Web Developer Course 2.0

Building Web APIs with ASP.NET Core

Build Progressive Web Apps with React

Build a web scraper with Node

Build a Basic Website with ASP.NET MVC and Angular

Build Your First PWA with Angular

Top 10 Podcasts for Web Developers