Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created December 3, 2015 13:09
Rendering Image Previews Using Object URLs vs. Base64 Data URIs In AngularJS
<!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