Drag And Drop Textures With WebGL


Your browser does not support the canvas tag. This is a static example of what would be seen.
Drop image files here


In this post I’m going build a WebGL app with basic 3D rendering. We’re going to render a rotating 3D box to a WebGL enabled canvas, and since that’s such a common thing to build as a first step into 3D rendering with a new graphics API, I’m going to mix in javascript drag and drop support at the same time so that images can be dropped into the demo and it should then update live so the texture ends up mapped across the box.

I’m going to need some 3D math functions. I don’t want to write my own if I can help it and as far as I know nothing suitable is built into javascript or WebGL, so I’m going to use the excellent glMatrix library, from Brandon Jones, which can be found at glMatrix.net with a good description of the library at stupidly-fast-webgl-matricies. It includes all the basic vec3, vec4 and mat4 ops I need for typical a rendering pipeline. From there we can easily build camera, model and projection matrices to feed to our shaders.

This drop zone for the drag and drop operation is going to be built from a standard HTML div, configured to look like a region where one might want to drop image files, so with a dotted line round the edge and a message encouraging the user to drop their files onto it, and we’ll also be setting up some listeners in our scripts to allow the dropped content to be picked up by the app, and if the app finds that it’s been sent an image file, then we’ll then use the javascript File API’s the pull the data in as a WebGL texture.

The drop zone itself ends up being as simple stack of HTML divs like this, with some CSS to establish that the outer box hash a dashed edge and to position the text vertically and horizontally inside the outer box (where positioning a box vertically is surprisingly complex!), like this…

<div class="dropZone_box" id="dropZone_box">
  <div class="dropZone_text_outer">
    <div class="dropZone_text" id="dropZone_text">
Drop image files here
    </div>
  </div>
</div>

The corresponding CSS looks like this…

.dropZone_box {
  border: 2px dashed #999;
  width: 256px;
  height: 64px;
  margin: 10px 0px 10px 0px;
  display: block;
  margin-right: auto;
  margin-left: auto;
}

.dropZone_text_outer {
  display: table;
  height: 100%;
  width: 100%;
}

.dropZone_text {
  text-align:center;
  display: table-cell;
  vertical-align: middle;
  margin-top: auto;
  margin-bottom: auto;
}

We then want to attach some event listeners to the outer div so that we can receive data. The event handler needs to receive a file, making sure we are dealing with an image file, and then read the file contents such that they can be fed to the texture loading function, and once it’s built the texture it should request an update of the WebGL canvas so that the new content appears immediately. We start by setting up two handlers, one for ‘dragover’ which will trigger when the mouse drag first hits the div, and another for ‘drop’ which will trigger when we let go of the mouse. This is how the code is going to end up looking.

var dropZone = document.getElementById('dropZone_box');

dropZone.addEventListener('dragover', function(evt) {	 	 
  evt.stopPropagation();	 	 
  evt.preventDefault();	 	 
  evt.dataTransfer.dropEffect = 'copy';	 	 
}, false);

dropZone.addEventListener('drop', function(evt) {
  evt.stopPropagation(); 
  evt.preventDefault();
  var files = evt.dataTransfer.files;
  for (var i = 0, f; f = files[i]; i++) {
    /* only process image files */
    if (!f.type.match('image.*')) {
      continue;
    }
    var reader = new FileReader();
    reader.onload = (function(theFile) {
      return function(e) {
        /* e.target.result can be used like a URL now */
        texture = loadTexture(gl, e.target.result, function(texture) {
          requestAnimationFrame(update);  
        });
      };
    })(f);
    /* read in the image file as a data URL */
    reader.readAsDataURL(f);
  }
}, false);

The ‘dragover’ handler is really just there to disable the default drag and drop handling. The important line is evt.preventDefault(). Without this the default handling of dragged images will instead be that the browser will simply load them. Try removing these lines and you’ll get a browser window filled with the image instead of the image being routed to the script. We don’t want that.

The ‘drop’ handler is where the bulk of our work needs to go. It will receive a list of files and using the HTML5 File API will then be able to read the properties and data from each of them. Note that we potentially process more than one image file here, which is kind of pointless as only one can ever end up filling our texture slot. The file API allows us to query the file type, in this case allowing us to match to the image type. Finally we use a series of event driven operations to first read the file as a URL (meaning we get a URL to the data that be passed to other functions), then to load the texture (from the URL), and finally when that finished we request our update.

Leave a Reply

Your email address will not be published. Required fields are marked *