Created
December 3, 2015 13:09
Rendering Image Previews Using Object URLs vs. Base64 Data URIs In AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html ng-app="Demo"> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Rendering Image Previews Using Object URLs vs. Base64 Data URIs In AngularJS | |
</title> | |
<link rel="stylesheet" type="text/css" href="./demo.css"></link> | |
</head> | |
<body ng-controller="AppController as app"> | |
<h1> | |
Rendering Image Previews Using Object URLs vs. Base64 Data URIs In AngularJS | |
</h1> | |
<p> | |
<a ng-click="app.toggleDemo()">Toggle Demos</a> (to clear them) | |
</p> | |
<!-- I show image previews using the URL.createObjectUrl() method. --> | |
<h2> | |
Rendering Previews Using URL.createObjectURL() | |
</h2> | |
<bn-demo ng-if="app.isShowingDemo" preview-method="url"> | |
<ul> | |
<li ng-repeat="image in demo.images track by image.id"> | |
<figure ng-style="{ 'background-image': 'url(' + image.src + ')' }"> | |
<figcaption> | |
{{ image.name }} | |
</figcaption> | |
</figure> | |
</li> | |
</ul> | |
</bn-demo> | |
<!-- I show image previews using the base64 method. --> | |
<h2> | |
Rendering Previews Using .getAsDataURL() | |
</h2> | |
<bn-demo ng-if="app.isShowingDemo" preview-method="base64"> | |
<ul> | |
<li ng-repeat="image in demo.images track by image.id"> | |
<figure ng-style="{ 'background-image': 'url(' + image.src + ')' }"> | |
<figcaption> | |
{{ image.name }} | |
</figcaption> | |
</figure> | |
</li> | |
</ul> | |
</bn-demo> | |
<!-- Load scripts. --> | |
<script type="text/javascript" src="../../vendor/angularjs/angular-1.4.7.min.js"></script> | |
<script type="text/javascript" src="../../vendor/plupload/2.1.8/moxie.js"></script> | |
<script type="text/javascript"> | |
// Create an application module for our demo. | |
angular.module( "Demo", [] ); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I control the root of the application. | |
angular.module( "Demo" ).controller( | |
"AppController", | |
function AppController( $scope ) { | |
var vm = this; | |
// I determine if the demo widgets are being shown. | |
vm.isShowingDemo = true; | |
// Expose public methods. | |
vm.toggleDemo = toggleDemo; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I toggle the display of the demo widgets. | |
function toggleDemo() { | |
vm.isShowingDemo = ! vm.isShowingDemo; | |
} | |
} | |
); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I provide a drag-n-drop image preview widget. | |
angular.module( "Demo" ).directive( | |
"bnDemo", | |
function bnDemoDirective( $document, $q ) { | |
// Return the directive definition object. | |
return({ | |
controller: DemoController, | |
controllerAs: "demo", | |
link: link, | |
restrict: "E" | |
}); | |
// I bind the JavaScript events to thew view-model. | |
function link( scope, element, attributes, controller ) { | |
// I am the means by which the image data will be rendered for this | |
// demo. Either as a local URL or as a base64-encoded data URI. | |
var previewMethod = ( attributes.previewMethod || "url" ); | |
// Set up our dropzone to accept image file types. | |
var dropzone = new mOxie.FileDrop({ | |
drop_zone: element[ 0 ], | |
accept: [ | |
{ | |
title: "Images", | |
extensions: "jpg,jpeg,gif,png" | |
} | |
] | |
}); | |
dropzone.ondrop = handleDrop; | |
dropzone.init(); | |
// When we render image previews using the URL service, it is | |
// recommended that the URLs be deallocated at some point to free up | |
// memory. As such, we're going to keep track of them and deallocate | |
// them in the $destroy event. | |
// -- | |
// NOTE: These URLs are also deallocated (automatically) when the | |
// current window is destroyed. | |
var localUrlsToDestroy = []; | |
// Listen for the destroy event to teardown the component. | |
scope.$on( "$destroy", handleDestroy ); | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I take the given image file and return a promise that resolves to | |
// the base64-based data URI. | |
function getPreviewData( file ) { | |
var deferred = $q.defer(); | |
// In this approach, we're using the mOxie Image class to | |
// asynchronously load the image into the browser memory and read | |
// the image as a base64 URI. | |
var preloader = new mOxie.Image(); | |
preloader.onload = handleLoad; | |
preloader.load( file ); | |
return( deferred.promise ); | |
function handleLoad() { | |
deferred.resolve( preloader.getAsDataURL() ); | |
preloader.destroy(); | |
} | |
} | |
// I take the given image file and return a promise that resolves to | |
// the local Object URL. | |
// -- | |
// NOTE: While accessing the Object URL is a SYNCHRONOUS process, I | |
// am wrapping it in a promise to make the preview access symmetric | |
// between this version and the base64 version (above). | |
function getPreviewUrl( file ) { | |
// In this approach, instead of reading the image into memory, | |
// we're going to get a URL that actually points to the image | |
// file in the local file system. | |
var localUrl = ( URL || webkitURL ).createObjectURL( file.getSource() ); | |
// Since it is recommended that we deallocate these object URLs, | |
// let's track it so that we can destroy it later. | |
localUrlsToDestroy.push( localUrl ); | |
return( $q.when( localUrl ) ); | |
} | |
// I tear down the component. | |
function handleDestroy() { | |
// Destroy the dropzone to prevent memory leaks. | |
// -- | |
// NOTE: This does not automatically destroy the dropped files - | |
// those have to be destroyed individually (see below). | |
dropzone.destroy(); | |
// While Object URLs are automatically destroyed when the | |
// window is destroyed, we can manually deallocate them when the | |
// component is destroyed since we no longer need them. | |
localUrlsToDestroy.forEach( | |
function iterator( localUrl ) { | |
( URL || webkitURL ).revokeObjectURL( localUrl ); | |
} | |
); | |
} | |
// I handle the dropping of an image object. | |
function handleDrop( event ) { | |
this.files.forEach( | |
function iterator( file ) { | |
// Read the image preview using the demo-specific method. | |
var promise = ( previewMethod === "url" ) | |
? getPreviewUrl( file ) | |
: getPreviewData( file ) | |
; | |
// Once the image preview is available, add the image to | |
// the demo. | |
// -- | |
// NOTE: $q is already taking care of the digest; hence, | |
// no need to call $apply() when changing the view-model. | |
promise.then( | |
function handleResolve( previewUrl ) { | |
controller.addImage( file.name, previewUrl ); | |
// Destroy the file to prevent memory leaks. | |
file.destroy(); | |
} | |
); | |
} | |
); | |
} | |
} | |
// I control the demo widget. | |
function DemoController( $scope ) { | |
var vm = this; | |
// I keep track of the auto-incrementing ID attached to each image. | |
var primaryKey = 0; | |
// I hold the image records to render. | |
vm.images = []; | |
// Expose public methods. | |
vm.addImage = addImage; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I add an image with the given name and preview src value. The | |
// image is added to the front of the collection. | |
function addImage( name, src ) { | |
vm.images.unshift({ | |
id: ++primaryKey, | |
name: name, | |
src: src | |
}); | |
} | |
} | |
} | |
); | |
// Turn off debugging for m0xie related functionality (quites the console). | |
MXI_DEBUG = false; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment