Build a Simple Game in Vanilla JS With the Drag and Drop API
Tags: javascript, api, gamedev
Published: 2021-09-21 16:54:14

---
title: Build a Simple Game in Vanilla JS With the Drag and Drop API
published: true
description: In this post, you'll learn about the Drag and Drop API and you'll also learn how to use it to build a simple game.
tags: javascript, api, gamedev
canonical_url: https://blog.jscrambler.com/build-a-simple-game-in-vanilla-js-with-the-drag-and-drop-api/
cover_image: https://blog.jscrambler.com/content/images/2021/08/jscrambler-blog-building-game-with-drag-drop-api.jpg
---

The JavaScript language lives in a browser. Actually, let's rephrase that: a web browser has a separate part inside of it, called the JavaScript engine. This engine can understand and run JS code.

There are many other separate parts that, altogether, make up the browser. These parts are different Browser APIs, also known as Web APIs. The JS engine is there to facilitate the execution of the JS code we write. Writing JS code is a way for us (developers) to access various functionalities that exist in the browser and that are exposed to us via Browser APIs.
## What Is a Web API?
Web APIs are known as "browser features". Arguably, the most popular of these various browser features⁠—at least for JavaScript developers⁠—is the browser console. The Console API allows us to log out the values of variables in our JavaScript code. Thus, we can manipulate values in our JS code and log out these values to verify that a specific variable holds a specific (expected) value at a certain point in the thread of execution, which is great for debugging. If you've spent any significant amount of time with the JS language, this should all be pretty familiar.

What some beginner JavaScript developers do not comprehend is the big picture of the browser having a large number of these "browser features" built-in⁠—and accessible to us via various JavaScript “facade” methods: methods that look like they’re just a part of the language itself, but are actually “facades” for features outside of the JS language itself. Some examples of widely used Web APIs are the DOM API, the Canvas API, the Fetch API, etc.

The JavaScript language is set up in such a way that we can't immediately infer that the functionality we're using is in fact a browser feature. For example, when we say:

```javascript
let heading = document.getElementById('main-heading');
```

... we're actually hooking into a browser feature⁠—but there's no way of knowing this since it looks like regular JS code.
## The Drag and Drop Web API

To understand how the Drag and Drop API works, and to effectively use it, all we have to do is know some basic concepts and methods it needs. Similar to how most front-end developers are familiar with the example from the previous section (namely, `document.getElementById`), we need to learn:

* the basic concepts of the Drag and Drop Web API;
* at least a few basic methods and commands.

The first important concept related to the Drag and Drop API is the concept of the source and target elements.
### Source and Target Elements

There are built-in browser behaviors that determine how certain elements will behave when a user clicks and drags them on the viewport. For example, if we try click-dragging the intro image of this very tutorial, we'll see a behavior it triggers: the image will be displayed as a semi-transparent thumbnail, on the side of our mouse pointer, following the mouse pointer for as long as we hold the click. The mouse pointer also changes to the following style:

```css
cursor: grabbing;
```

We've just shown an example of an element becoming a **source element** for a drag-and-drop operation. The target of such an operation is known as the **target element**.

Before we cover an actual drag and drop operation, let's have a quick revision of events in JS.
## Events in JS: A Quick Revision
We could go as far as saying that events are the foundation on which all our JavaScript code rests. As soon as we need to do something **interactive** on a web page, events come into play.

In our code, we listen for: mouse clicks, mouse hovers (mouseover events), scroll events, keystroke events, document loaded events...

We also write **event handlers** that take care of executing some JavaScript code to handle these events.

We say that we listen for events firing and that we write event handlers for the events being fired.
## Describing a Drag and Drop Operation, Step By Step

### The HTML and CSS

Let's now go through a minimal drag and drop operation. We'll describe the theory and concepts behind this operation as we go through each step.

The example is as easy as can be: there are two elements on a page. They are styled as boxes. The first one is a little box and the second one is a big box.

To make things even easier to comprehend, let's "label" the first box as "source", and the second one as "target":

```html
<div id="source">Source</div>
<div id="target">Target</div>
<style>
    #source {
        background: wheat;
        width: 100px;
        padding: 20px;
        text-align: center;
    }

#target {
    background: #abcdef;
    width: 360px;
    height: 180px;
    padding: 20px 40px;
    text-align: center;
    margin-top: 50px;
    box-sizing: border-box;
}
</style>
```

A little CSS caveat above: to avoid the added border width increasing the width of the whole target `div`, we’ve added the CSS property-value pair of `box-sizing: border-box` to the `#target` CSS declaration. Thus, the target element has consistent width, regardless of whether our drag event handlers are running or not.

The output of this code is fairly straightforward:

<center><img src="https://blog.jscrambler.com/content/images/2021/08/jscrambler-blog-build-a-game-in-js-1.png" alt="jscrambler-blog-build-a-game-in-js-1"></center>



### Convert the “Plain” HTML Element Into a Drag and Drop Source Element

To do this, we use the `draggable` attribute, like so:

```html
<div id="source" draggable="true">Source</div>
```

What this little addition does is changing the behavior of the element. Before we added the `draggable` attribute, if a user click-dragged on the Source `div`, they'd likely just highlight the text of the `div` (i.e the word "Source")⁠—as if they were planning to select the text before copying it.

However, with the addition of the `draggable` attribute, the element changes its behavior and behaves exactly like a regular HTML `img` element — we even get that little `grabbed` cursor⁠—giving an additional signal that we've triggered the drag-and-drop functionality.

### Capture Drag and Drop Events

There are 8 relevant events in this API:

1. drag
2. dragstart
3. dragend
4. dragover
5. dragenter
6. dragleave
7. drop
8. dragend

During a drag and drop operation, a number of the above events can be triggered: maybe even all of them. However, we still need to write the code to react to these events, using event handlers, as we will see next.
### Handling the Dragstart and Dragend Events

We can begin writing our code easily. To specify which event we’re handling, we’ll just add an `on` prefix.

For example, in our HTML code snippet above, we’ve turned a “regular” HTML element into a source element for a drag-and-drop operation. Let’s now handle the `dragstart` event, which fires as soon as a user has started dragging the source element:

```javascript
let sourceElem = document.getElementById('source');
sourceElem.addEventListener('dragstart', function (event) {
    confirm('Are you sure you want to move this element?');
})
```

All right, so we’re reacting to a `dragstart` event, *i.e.* we’re handling the `dragstart` event.

Now that we know we can handle the event, let’s react to the event firing by changing the styles of the source element and the target element.

```javascript
let sourceElem = document.getElementById('source');
let targetElem = document.getElementById('target');
sourceElem.addEventListener('dragstart', function (event) {
    event.currentTarget.style = "opacity:0.3";
    targetElem.style = "border: 10px dashed gray;";
})
```

Now, we’re handling the `dragstart` event by making the source element see-through, and the target element gets a big dashed gray border so that the user can more easily see what we want them to do.

It’s time to undo the style changes when the `dragend` event fires (*i.e.* when the user releases the hold on the left mouse button):

```javascript
sourceElem.addEventListener('dragend', function (event) {
    sourceElem.style = "opacity: 1";
    targetElem.style = "border: none";
})
```

Here, we’ve used a slightly different syntax to show that there are alternative ways of updating the styles on both the source and the target elements. For the purposes of this tutorial, it doesn’t really matter what kind of syntax we choose to use.
### Handling the Dragover and Drop Events

It’s time to deal with the `dragover` event. This event is fired from the target element.

```javascript
targetElem.addEventListener('dragover', function (event) {
    event.preventDefault();
});
```

All we’re doing in the above function is preventing the default behavior (which is opening as a link for specific elements). In a nutshell, we’re setting the stage for being able to perform some operation once the `drop` event is triggered.

Here’s our `drop` event handler:

```javascript
targetElem.addEventListener('drop', function (event) {
    console.log('DROP!');
})
```

Currently, we’re only logging out the string `DROP!` to the console. This is good enough since it’s proof that we’re going in the right direction.

---
**Sidenote**: notice how some events are emitted from the source element, and some other events are emitted from the target element. Specifically, in our example, the `sourceElem` element emits the `dragstart` and the `dragend` events, and the `targetElem` emits the `dragover` and `drop` events.
---

Next, we’ll use the `dataTransfer` object to move the source element onto the target element.

## Utilize The dataTransfer Object
The `dataTransfer` object “lives” in an instance of the Event object⁠—which is built-in to any event. We don’t have to “build” the event object⁠—we can simply pass it to the anonymous event handler function⁠—since functions are “first-class citizens” in JS (meaning: we can pass them around like any other value)⁠—this allows us to pass anonymous functions to event handlers, such as the example we just saw in the previous section.

In that piece of code, the second argument we passed to the `addEventListener()` method is the following anonymous function:
```javascript
function(event) {
  console.log('DROP!');
}
```

The `event` argument is a built-in object, an instance of the `Event` object. This `event` argument comes with a number of properties and methods, including the `dataTransfer` property⁠, which itself is an object.

In other words, we have the following situation (warning: pseudo-code ahead!):

```javascript
event: {
…,
dataTransfer: {…},
stopPropagation: function(){…},
preventDefault: function(){…},
…,
…,
}
```

The important thing to conclude from the above structure is that the `event` object is just a JS object holding other values, including nested objects and methods. The `dataTransfer` object is just one such nested object, which comes with its own set of properties/methods.

In our case, we’re interested in the `setData()` and `getData()` methods on the `dataTransfer` object.
### The `setData()` and `getData()` Methods on The `dataTransfer` Object
To be able to successfully “copy” the source element onto the target element, we have to perform a few steps:

1. We need to hook into an event handler for an appropriate drag-and-drop related event, as it emits from the source object;
2. Once we’re hooked into that specific event (*i.e.* once we’ve completed the step above), we’ll need to use the `event.dataTransfer.setData()` method to pass in the relevant HTML⁠—which is, of course, the source element;
3. We’ll then hook into an event handler for another drag-and-drop event⁠—this time, an event that’s emitting from the target object;
4. Once we’re hooked into the event handler in the previous step, we’ll need to get the data from step two, using the following method: `event.dataTransfer.getData()`.

That’s all there is to it! To reiterate, we’ll:

1. Hook into the `dragstart` event and use the `event.dataTransfer.setData()` to pass in the source element to the `dataTransfer` object;
2. Hook into the `drop` event and use the `event.dataTransfer.getData()` to get the source element’s data from the `dataTransfer` object.

So, let’s hook into the `dragstart` event handler and get the source element’s data:

```javascript
sourceElem.addEventListener('dragstart', function(event) {
event.currentTarget.style="opacity:0.3";
targetElem.style = "border: 10px dashed gray;";
event.dataTransfer.setData('text', event.target.id);
})
```

Next, let’s pass this data on to the `drop` event handler:
```javascript
targetElem.addEventListener('drop', function(event) {
console.log('DROP!');
event.target.appendChild(document.getElementById(event.dataTransfer.getData('text')));
})
```

We could rewrite this as:
```javascript
targetElem.addEventListener('drop', function(event) {
console.log('DROP!');
const sourceElemData = event.dataTransfer.getData('text');
const sourceElemId = document.getElementById(sourceElemData);
event.target.appendChild(sourceElemId);
})
```

Regardless of how we decide to do this, we’ve completed a simple drag-and-drop operation, from start to finish.

Next, let’s use the Drag and Drop API to build a game.

## Write a Simple Game Using the Drag and Drop API

In this section, we’ll build a very, very, simple game. We’ll have a row of spans with jumbled lyrics to a famous song.

The user can now drag and drop the words from the first row onto the empty slots in the second row, as shown below.

<center><img src="https://blog.jscrambler.com/content/images/2021/08/jscrambler-blog-build-a-game-in-js-2.png" alt="jscrambler-blog-build-a-game-in-js-2"></center>


The goal of the game is to place the lyrics in the correct order.

Let’s begin by adding some HTML structure and CSS styles to our game.

### Adding The game’s HTML and CSS

```html
<h1>Famous lyrics game: Abba</h1>
<h2>Instruction: Drag the lyrics in the right order.</h2>
<div id="jumbledWordsWrapper">
  <span id="again" data-source-id="again" draggable="true">again</span>
  <span id="go" data-source-id="go" draggable="true">go</span>
  <span id="I" data-source-id="I" draggable="true">I</span>
  <span id="here" data-source-id="here" draggable="true">here</span>
  <span id="mia" data-source-id="mia" draggable="true">mia</span>
  <span id="Mamma" data-source-id="Mamma" draggable="true">Mamma</span
</div>
<div id="orderedWordsWrapper">
  <span data-target-id="Mamma"></span>
  <span data-target-id="mia"></span>
  <span data-target-id="here"></span>
  <span data-target-id="I"></span>
  <span data-target-id="go"></span>
  <span data-target-id="again"></span>
</div>
```

The structure is straightforward. We have the static `h1` and `h2` tags. Then, we have the two divs:
* `jumbledWordsWrapper`, and
* `orderedWordsWrapper`

Each of these wrappers holds a number of span tags: one span tag for each word. The span tags in the `orderedWordsWrapper` don’t have any text inside, they’re empty.

We’ll use CSS to style our game, as follows:

```css
body {
  padding: 40px;
}
h2 {
  margin-bottom: 50px;
}
#jumbledWordsWrapper span {
  background: wheat;
  box-sizing: border-box;
  display: inline-block;
  width: 100px;
  height: 50px;
  padding: 15px 25px;
  margin: 0 10px;
  text-align: center;
  border-radius: 5px;
  cursor: pointer;  
}
#orderedWordsWrapper span {
  background: #abcdef;
  box-sizing: border-box;
  text-align: center;
  margin-top: 50px;
}
```

Next, we’ll add some behavior to our game using JavaScript.
### Adding Our Game’s JavaScript Code

We’ll start our JS code by setting a couple of variables and logging them out to make sure we’ve got proper collections:

```javascript
const jumbledWords = document.querySelectorAll('#jumbledWordsWrapper > span');
const orderedWords = document.querySelectorAll('#orderedWordsWrapper > span');
console.log('jumbledWords: ', jumbledWords);
console.log('orderedWords: ', orderedWords);
```

The output in the console is as follows:
```
"jumbledWords: " // [object NodeList] (6)
["<span/>","<span/>","<span/>","<span/>","<span/>","<span/>"]
"orderedWords: " // [object NodeList] (6)
["<span/>","<span/>","<span/>","<span/>","<span/>","<span/>"]
```

Now that we’re sure that we’re capturing the correct collections, let’s add an event listener on each of the members in the two collections.

On all the source elements, we’ll add methods to handle the `dragstart` event firing:
```javascript
jumbledWords.forEach(el => {
  el.addEventListener('dragstart', dragStartHandler);
})
function dragStartHandler(e) {
  console.log('dragStartHandler running');
  e.dataTransfer.setData('text', e.target.getAttribute('data-source-id'));
  console.log(e.target);
}
```

On all the target elements, we’ll add methods to handle all the relevant drag-and-drop events, namely:
* `dragenter`
* `dragover`
* `dragleave`
* `drop`

The code that follows should be already familiar:

```javascript
orderedWords.forEach(el => {
  el.addEventListener('dragenter', dragEnterHandler);
  el.addEventListener('dragover', dragOverHandler);
  el.addEventListener('dragleave', dragLeaveHandler);
  el.addEventListener('drop', dropHandler);
})

function dragEnterHandler(e) {
  console.log('dragEnterHandler running');
}

function dragOverHandler(e) {
  console.log('dragOverHandler running');
  event.preventDefault();
}

function dragLeaveHandler(e) {
  console.log('dragLeaveHandler running');
}

function dropHandler(e) {
  e.preventDefault();
  
  console.log('dropHandler running');
  
  const dataSourceId = e.dataTransfer.getData('text'); 
  const dataTargetId = e.target.getAttribute('data-target-id');
  console.warn(dataSourceId, dataTargetId);

  if(dataSourceId === dataTargetId) {
    console.log(document.querySelector([dataTargetId]));
    e.target.insertAdjacentHTML('afterbegin', dataSourceId);
  }
}
```

In the `dropHandler()` method, we’re preventing the default way that the browser handles the data that comes in. Next, we’re getting the dragged element’s data and we’re saving it in `dataSourceId`, which will be the first part of our matching check. Next, we get the `dataTargetId` so that we can compare if it is equal to the `dataSourceId`.

If `dataSouceId` and `dataTargetId` are equal, that means that our custom data attributes hold matching values, and thus we can complete the adding of the specific source element’s data into the specific target element’s HTML.
### Adding CSS Code For Better UX

Let’s start by inspecting the complete JS code, made slimmer by removal of all redundant `console.log()` calls.

```javascript
const jumbledWords = document.querySelectorAll('#jumbledWordsWrapper > span');
const orderedWords = document.querySelectorAll('#orderedWordsWrapper > span');

jumbledWords.forEach(el => {
  el.addEventListener('dragstart', dragStartHandler);
})
orderedWords.forEach(el => {
  el.addEventListener('dragenter', dragEnterHandler);
  el.addEventListener('dragover', dragOverHandler);
  el.addEventListener('dragleave', dragLeaveHandler);
  el.addEventListener('drop', dropHandler);
})

function dragStartHandler(e) {
  e.dataTransfer.setData('text', e.target.getAttribute('data-source-id'));
}

function dragEnterHandler(e) {
}

function dragOverHandler(e) {
  event.preventDefault();
}

function dragLeaveHandler(e) {
}

function dropHandler(e) {
  e.preventDefault();
  
  const dataSourceId = e.dataTransfer.getData('text'); 
  const dataTargetId = e.target.getAttribute('data-target-id');

  if(dataSourceId === dataTargetId) {
    e.target.insertAdjacentHTML('afterbegin', dataSourceId);
  }
}
```

As you can verify above, we’ve removed all the `console.log()` invocations, and thus some of our event handler functions are now empty. 

That means these functions are ready to receive corresponding CSS code updates. Additionally, due to the updates in style to the `dragStartHandler()` method, we’ll also need to add a brand new source element’s event listener for the `dragend` event.

We’ll start by adding another event listener to the `jumbledWords` collection:
```javascript
jumbledWords.forEach(el => {
  el.addEventListener('dragstart', dragStartHandler);
  el.addEventListener('dragend', dragEndHandler);
})
```

And we’ll update the two event handler function definitions too:

```javascript
function dragStartHandler(e) {
  e.dataTransfer.setData('text', e.target.getAttribute('data-source-id'));
  e.target.style = 'opacity: 0.3';
}
function dragEndHandler(e) {
  e.target.style = 'opacity: 1';
}
```

Next, we’ll update the styles inside the `dragEnterhandler()` and the `dragLeaveHandler()` methods.

```javascript
function dragEnterHandler(e) {
  e.target.style = 'border: 2px dashed gray; box-sizing: border-box; background: whitesmoke';
}
function dragLeaveHandler(e) {
  e.target.style = 'border: none; background: #abcdef';
}
```

We’ll also go around some styling issues by updating the if condition inside the `dropHandler()` method:

```javascript
  if(dataSourceId === dataTargetId) {
    e.target.insertAdjacentHTML('afterbegin', dataSourceId);
    e.target.style = 'border: none; background: #abcdef';
    e.target.setAttribute('draggable', false);
  }
```

### Preventing Errors
We’ve set up our JS code so that it checks if the values are matching for the `data-source-id` of the `jumbledWordsWrapper` div and the `data-target-id` of the `orderedWordsWrapper` div.

This check in itself prevents us from dragging any other word onto the correct place⁠—except for the matching word.

However, we have a bug: there’s no code preventing us from dragging the correct word into the same span inside the `orderedWordsWrapper` multiple times.

Here’s an example of this bug:

<center><img src="https://blog.jscrambler.com/content/images/2021/08/jscrambler-blog-build-a-game-in-js-3.png" alt"jscrambler-blog-build-a-game-in-js-3"></center>

Obviously, this is a bug that we need to fix. Luckily, the solution is easy: we’ll just get the source element’s `data-source-id`, and we’ll use it to build a string which we’ll then use to run the `querySelector` on the entire document. This will allow us to find *the one* source span tag whose text node we used to pass it to the correct target slot. Once we do that, all we need to do is set the `draggable` attribute to `false` (on the source span element)⁠, thus preventing the already used source span element from being dragged again.

```javascript
  if(dataSourceId === dataTargetId) {
    e.target.insertAdjacentHTML('afterbegin', dataSourceId);
    e.target.style = 'border: none; background: #abcdef';
    
    let sourceElemDataId = 'span[data-source-id="' + dataSourceId + '"]';
    let sourceElemSpanTag = document.querySelector(sourceElemDataId);
```

Additionally, we can give our source element the styling to indicate that it’s no longer draggable. A nice way to do it is to add another attribute: a `class` attribute. We can do this with `setAttribute` syntax, but here’s another approach, using `Object.assign()`:

```javascript
    Object.assign(sourceElemSpanTag, {
      className: 'no-longer-draggable',
    });
```

The above syntax allows us to set several attributes, and thus we can also set `draggable` to `false` as the second entry:
```javascript
    Object.assign(sourceElemSpanTag, {
      className: 'no-longer-draggable',
      draggable: false,
    });
```

Of course, we also need to update the CSS with the `no-longer-draggable` class:

```css
.no-longer-draggable {
  cursor: not-allowed !important;
  background: lightgray !important;
  opacity: 0.5 !important;
}
```

We have an additional small bug to fix: earlier in the tutorial, we’ve defined the `dragEnterHandler()` and the `dragLeaveHandler()` functions. The former function sets the styles on the dragged-over target to a dotted border and a pale background, which signals a possible drop location. The latter function reverts these styles to `border: none; background: #abcdef`. However, our bug occurs if we drag and try to drop a word into the wrong place. This happens because the `dragEnterHandler()` event handler gets called as we drag over the wrong word, but since we never trigger the `dragLeaveHandler()`⁠—instead, we triggered the `dropHandler()` function⁠—the styles never get reverted.

The solution for this is really easy: we’ll just run the `dragLeaveHandler()` at the top of the `dropHandler()` function definition, right after the `e.preventDefault()`, like this:

```javascript
function dropHandler(e) {
  e.preventDefault();
  dragLeaveHandler(e); 
```

Now our simple game is complete!

Here’s the full, completed JavaScript code:

```javascript
const jumbledWords = document.querySelectorAll('#jumbledWordsWrapper > span');
const orderedWords = document.querySelectorAll('#orderedWordsWrapper > span');

jumbledWords.forEach(el => {
  el.addEventListener('dragstart', dragStartHandler);
  el.addEventListener('dragend', dragEndHandler);
})
orderedWords.forEach(el => {
  el.addEventListener('dragenter', dragEnterHandler);
  el.addEventListener('dragover', dragOverHandler);
  el.addEventListener('dragleave', dragLeaveHandler);
  el.addEventListener('drop', dropHandler);
})

function dragStartHandler(e) {
  e.dataTransfer.setData('text', e.target.getAttribute('data-source-id'));
  e.target.style = 'opacity: 0.3';
}
function dragEndHandler(e) {
  e.target.style = 'opacity: 1';
}

function dragEnterHandler(e) {
  e.target.style = 'border: 2px dashed gray; box-sizing: border-box; background: whitesmoke';
}

function dragOverHandler(e) {
  event.preventDefault();
}

function dragLeaveHandler(e) {
  e.target.style = 'border: none; background: #abcdef';
}

function dropHandler(e) {
  e.preventDefault();
  dragLeaveHandler(e); 
  
  const dataSourceId = e.dataTransfer.getData('text'); 
  const dataTargetId = e.target.getAttribute('data-target-id');
  
  if(dataSourceId === dataTargetId) {
    e.target.insertAdjacentHTML('afterbegin', dataSourceId);
    e.target.style = 'border: none; background: #abcdef';
    
    let sourceElemDataId = 'span[data-source-id="' + dataSourceId + '"]';
    let sourceElemSpanTag = document.querySelector(sourceElemDataId);
    
    Object.assign(sourceElemSpanTag, {
      className: 'no-longer-draggable',
      draggable: false,
    });
  }
  
}
```
## Final Thoughts

Even though our game is finished, this doesn’t have to be the end of the road! 

It’s always possible to further improve our code. There are many additional things that can be done here.

We could, for example:
* Add a start and end screen;
* Add a counter that would count the number of attempts;
* Add a countdown timer that would not limit the number of attempts, but rather the time available for us to complete our puzzle game;
* Add more questions;
* Add a leaderboard (we’d need to persist our data somehow);
* Refactor the logic of our game so that we can keep the questions and the order of words in a simple JS object;
* Fetch the questions from a remote API.

There are always more things to learn and more ways to expand our apps. However, all of the above-listed improvements are out of the scope of this tutorial⁠—learning the basics of the Drag and Drop API by building a simple game in vanilla JS.

The fun of coding is in the fact that you can try things on your own. So, try to implement some of these improvements, and make this game your own.

Lastly, if you want to secure your JavaScript source code against theft and reverse-engineering, you can <a href="https://jscrambler.com/signup?utm_source=blog.jscrambler.com&utm_medium=referral&utm_campaign=build-simple-game">try Jscrambler</a> for free.

---

Originally published on the [Jscrambler Blog](https://blog.jscrambler.com/build-a-simple-game-in-vanilla-js-with-the-drag-and-drop-api/?utm_source=dev.to&utm_medium=referral&utm_campaign=build-simple-game) by [Ajdin Imsirovic](https://blog.jscrambler.com/author/ajdin?utm_source=dev.to&utm_medium=referral&utm_campaign=build-simple-game).