ngJuxtapose

AngularJS directive for JuxtaposeJS, a JavaScript library for making before/after image sliders.

Learn More Code on Github Download (1.0.2)

Simplest usage possible! Just add the directive in your html page.

<juxtapose
  before-image-url="'http://online.wsj.com/media/LIONDOORA.jpg'" 
  after-image-url="'http://online.wsj.com/media/LIONDOOR_2A.jpg'">
</juxtapose>
							  
The before image refers to the image on the left (resp. on top) in case of vertical (resp. horizontal) orientation.
The after image refers to the image on the right (resp. at the bottom) in case of vertical (resp. horizontal) orientation.

A little more complete usage, with some options given via hardcoded values.

<juxtapose 
  before-image-url="'http://online.wsj.com/media/LIONDOORA.jpg'" 
  before-image-label="'Maidan square in 2009'" 
  before-image-credit="'WSJ'"

  after-image-url="'http://online.wsj.com/media/LIONDOOR_2A.jpg'" 
  after-image-label="'Maidan square in 2014'" 
  after-image-credit="">
</juxtapose>
							  

The following options are available on the directive element, and directly affect the two images:

  • before-image-url : the URL to the before image
  • before-image-label : the label of the before image (displayed on top of the image)
  • before-image-credit : the credit for the before image (displayed as overlay, below the 2 images)
  • before-image-alt : the alternate text (alt) for the before image
  • after-image-url : the URL to the after image
  • after-image-label : the label of the after image (displayed as overlay, on top of the image)
  • after-image-credit : the credit for the after image (displayed below the 2 images)
  • after-image-alt : the alternate text (alt) for the after image

The following options are available on both the directive element and as config parameters (see juxtaposeConfigProvider), and globally affect the slider itself:

  • starting-position : the slider start position (default is 50%, at the middle of the two images)
  • show-labels : indicates if image labels must be displayed. You must have labels for both images, or they won't be shown, even if true is set (default value)
  • show-credits : indicates if image credits must be displayed. You must have credits for both images, or they won't be shown, even if true is set (default value)
  • animate : If true, the divider will glide to the point where someone clicks; otherwise, it will jump (default is true)
  • vertical : If true, the divider will move vertically instead of horizontally (default is true)

When config options are defined as attributes, they take precedence over values defined via the juxtaposeConfigProvider

Another usage, with some options given via scope values.

<div ng-controller="MyCtrl">
  <juxtapose
	before-image-url="options.beforeImageUrl"
	before-image-label="options.beforeImageLabel"
	before-image-credit="options.beforeImageCredit"
	before-image-alt="options.beforeImageAlt"

	after-image-url="options.afterImageUrl"
	after-image-label="options.afterImageLabel"
	after-image-credit="options.afterImageCredit"
	after-image-alt="options.afterImageAlt">
  </juxtapose>
</div>
							  
angular.module('ngJuxtaposeDemo').controller('MyCtrl', ['$scope',  function ($scope) {
  $scope.options = {};
  $scope.options.beforeImageUrl = 'http://online.wsj.com/media/LIONDOORA.jpg';
  $scope.options.beforeImageLabel = '2009';
  $scope.options.beforeImageCredit = 'WSJ';
  $scope.options.beforeImageAlt = '2009 Alternate text';

  $scope.options.afterImageUrl = 'http://online.wsj.com/media/LIONDOOR_2A.jpg';
  $scope.options.afterImageLabel = '2014';
  $scope.options.afterImageCredit = 'TKO';
  $scope.options.afterImageAlt = '2014 Alternate text';

}]);
							  
Before Image:
After Image:
<div  ng-controller="HolyGrailCtrl">
  <juxtapose
	starting-position="options.startingPosition"
	show-labels="options.showLabels"
	show-credits="options.showCredits"
	animate="options.animate"
	vertical="options.vertical"

	before-image-url="options.beforeImageUrl"
	before-image-label="options.beforeImageLabel"
	before-image-credit="options.beforeImageCredit"
	before-image-alt="options.beforeImageAlt"

	after-image-url="options.afterImageUrl"
	after-image-label="options.afterImageLabel"
	after-image-credit="options.afterImageCredit"
	after-image-alt="options.afterImageAlt">
  </juxtapose>

  <h1>Options</h1>
  
  <form  name="optionsForm" role="form" class="form-horizontal" novalidate>
  	<fieldset>
  		<div class="form-group">
  			<strong>Before Image:</strong>
  		</div>

  		<div class="form-group" ng-class="{'has-error': optionsForm.beforeImageUrl.$error.required}">
  			<input type="text" class="form-control" name="beforeImageUrl" ng-model="options.beforeImageUrl" placeholder="URL (mandatory)" required>
  		</div>
  	
  		<div class="form-group">
  			<div class="row">
  				<div class="col-xs-12 col-sm-6 col-md-6">
  					<input type="text" class="form-control" ng-model="options.beforeImageLabel" placeholder="Label (optional)">
  				</div>
  				<div class="col-xs-12 col-sm-6 col-md-6">
  					<input type="text" class="form-control" ng-model="options.beforeImageCredit" placeholder="Credit (optional)">
  				</div>
  			</div>
  		</div>

  		<div class="form-group">
  			<input type="text" class="form-control" ng-model="options.beforeImageAlt" placeholder="Alternate Text (optional)" required>
  		</div>
  	</fieldset>

  	<fieldset>
  		<div class="form-group">
  			<strong>After Image:</strong>
  		</div>
  		
  		<div class="form-group" ng-class="{'has-error': optionsForm.afterImageUrl.$error.required}">
  			<input type="text" class="form-control" name="afterImageUrl" ng-model="options.afterImageUrl" placeholder="URL (mandatory)" required>
  		</div>
  	
  		<div class="form-group">
  			<div class="row">
  				<div class="col-xs-12 col-sm-6 col-md-6">
  					<input type="text" class="form-control" ng-model="options.afterImageLabel" placeholder="Label (optional)">
  				</div>
  				<div class="col-xs-12 col-sm-6 col-md-6">
  					<input type="text" class="form-control" ng-model="options.afterImageCredit" placeholder="Credit (optional)">
  				</div>
  			</div>
  		</div>

  		<div class="form-group">
  			<input type="text" class="form-control" ng-model="options.afterImageAlt" placeholder="Alternate Text (optional)" required>
  		</div>
  	</fieldset>

  	<fieldset>
  		<div class="row">
  			<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
  				<div class="checkbox-inline">
  					<label>
  						<input type="checkbox" ng-model="options.showLabels">
  						Show Labels ?
  					</label>
  				</div>

  				<div class="checkbox-inline">
  					<label>
  						<input type="checkbox" ng-model="options.showCredits">
  						Show Credits ?
  					</label>
  				</div>
  
  				<div class="checkbox-inline">
  					<label>
  						<input type="checkbox" ng-model="options.animate">
  						Animate Slider ?
  					</label>
  				</div>

  				<div class="checkbox-inline">
  					<label>
  						<input type="checkbox" ng-model="options.vertical">
  						Vertical Slider ?
  					</label>
  				</div>
  			</div>
  		</div>
  	</fieldset>
  </form>
</div>
							  
angular.module('ngJuxtaposeDemo').controller('HolyGrailCtrl', ['$scope', function ($scope) {
  $scope.options = {};

  $scope.options.startingPosition = '30%';
  $scope.options.showLabels = true;
  $scope.options.showCredits = true;
  $scope.options.animate = true;
  $scope.options.vertical = false;

  $scope.options.beforeImageUrl = 'https://juxtapose.knightlab.com/static/img/Sochi_11April2005.jpg';
  $scope.options.beforeImageLabel = 'April 2005';
  $scope.options.beforeImageCredit = '';
  $scope.options.beforeImageAlt = '2009 Alternate text';

  $scope.options.afterImageUrl = 'https://juxtapose.knightlab.com/static/img/Sochi_22Nov2013.jpg';
  $scope.options.afterImageLabel = 'Nov 2013';
  $scope.options.afterImageCredit = '';
  $scope.options.afterImageAlt = '';

}]);

							  

You can change how JuxtaposeJS appears in the browser by overriding the CSS rules. More information can be found here

<juxtapose
	before-image-url="'http://online.wsj.com/media/LIONDOORA.jpg'" 
	after-image-url="'http://online.wsj.com/media/LIONDOOR_2A.jpg'">
</juxtapose>
								  
div.juxtapose {
	width: 100%;
	font-family: Helvetica, Arial, sans-serif;
}

div.jx-slider {
	width: 100%;
	height: 100%;
	position: relative;
	overflow: hidden;
	cursor: pointer;
}

div.jx-handle {
	position: absolute;
	height: 100%;
	width: 40px;
	cursor: col-resize;
	z-index: 10;
	margin-left: -20px;
}


.vertical div.jx-handle {
	height: 40px;
	width: 100%;
	cursor: row-resize;
	margin-top: -20px;
	margin-left: 0;
}

div.jx-control {
	height: 100%;
	margin-right: auto;
	margin-left: auto;
	width: 3px;
	background-color: white;
}

.vertical div.jx-control {
	height: 3px;
	width: 100%;
	background-color: white;
	position: relative;
	top: 50%;
	transform: translateY(-50%);
}

div.jx-controller {
	position: absolute;
	margin: auto;
	top: 0;
	bottom: 0;
	height: 60px;
	width: 9px;
	margin-left: -3px;
	background-color: white;
}

.vertical div.jx-controller {
	height: 9px;
	width: 100px;
	margin-left: auto;
	margin-right: auto;
	top: -3px;
	position: relative;
}

div.jx-arrow {
	position: absolute;
	margin: auto;
	top: 0;
	bottom: 0; 
	width: 0;
	height: 0;
	transition: all .2s ease;
}

.vertical div.jx-arrow {
	position: absolute;
	margin: 0 auto;
	left: 0;
	right: 0; 
	width: 0;
	height: 0;
	transition: all .2s ease;
}


div.jx-arrow.jx-left {
	left: 2px;
	border-style: solid;
	border-width: 8px 8px 8px 0;
	border-color: transparent #FFF transparent transparent;
}

div.jx-arrow.jx-right {
	right: 2px;
	border-style: solid;
	border-width: 8px 0 8px 8px;
	border-color: transparent transparent transparent #FFF;
}

.vertical div.jx-arrow.jx-left {
	left: 0px;
	top: 2px;
	border-style: solid;
	border-width: 0px 8px 8px 8px;
	border-color: transparent transparent #FFF transparent;
}

.vertical div.jx-arrow.jx-right {
	right: 0px;
	top: initial;
	bottom: 2px;
	border-style: solid;
	border-width: 8px 8px 0 8px;
	border-color: #FFF transparent transparent transparent;
}

div.jx-handle:hover div.jx-arrow.jx-left,
div.jx-handle:active div.jx-arrow.jx-left {
	left: -1px;
}

div.jx-handle:hover div.jx-arrow.jx-right, 
div.jx-handle:active div.jx-arrow.jx-right {
	right: -1px;
}

.vertical div.jx-handle:hover div.jx-arrow.jx-left,
.vertical div.jx-handle:active div.jx-arrow.jx-left {
	left: 0px;
	top: 0px;
}

.vertical div.jx-handle:hover div.jx-arrow.jx-right, 
.vertical div.jx-handle:active div.jx-arrow.jx-right {
	right: 0px;
	bottom: 0px;
}


div.jx-image {
	position: absolute;
	height: 100%;
	display: inline-block;
	top: 0;
	overflow: hidden;
	-webkit-backface-visibility: hidden;
	-webkit-transform: scale(1);
}

.vertical div.jx-image {
	width: 100%;
	left: 0;
	top: initial;
}

div.jx-image img {
	height: 100%;
	z-index: 5;
	position: absolute;
	max-height: initial;
	max-width: initial;
}

.vertical div.jx-image img {
	height: initial;
	width: 100%;
}

div.jx-image.jx-left {
	left: 0;
	background-position: left;
}

div.jx-image.jx-left img {
	left: 0;
}

div.jx-image.jx-right {
	right: 0;
	background-position: right;
}

div.jx-image.jx-right img {
	right: 0;
	bottom: 0;
}


.veritcal div.jx-image.jx-left {
	top: 0;
	background-position: top;
}

.veritcal div.jx-image.jx-left img {
	top: 0;
}

.vertical div.jx-image.jx-right {
	bottom: 0;
	background-position: bottom;
}

.veritcal div.jx-image.jx-right img {
	bottom: 0;
}


div.jx-image div.jx-label {
	font-size: 1em;
	padding: .25em .75em;
	position: relative;
	display: inline-block;
	top: 0;
	background-color: #000; /* IE 8 */
	background-color: rgba(0,0,0,.7);
	color: white;
	z-index: 10;
	white-space: nowrap;
	line-height: 18px;
	vertical-align: middle;
}

div.jx-image.jx-left div.jx-label {
	float: left;
	left: 0;
}

div.jx-image.jx-right div.jx-label {
	float: right;
	right: 0;
}

.vertical div.jx-image div.jx-label {
	display: table;
	position: absolute;
}

.vertical div.jx-image.jx-right div.jx-label {
	left: 0;
	bottom: 0;
	top: initial;
}

div.jx-credit {
	line-height: 1.1;
	font-size: 0.75em;
}

div.jx-credit em {
	font-weight: bold;
	font-style: normal;
}


/* Animation */

div.jx-image.transition {
	transition: width .5s ease;
}

div.jx-handle.transition {
	transition: left .5s ease;
}

.vertical div.jx-image.transition {
	transition: height .5s ease;
}

.vertical div.jx-handle.transition {
	transition: top .5s ease;
}

/* Knight Lab Credit */
a.jx-knightlab {
	background-color: #000; /* IE 8 */
	background-color: rgba(0,0,0,.25);
	bottom: 0;
	display: table;
	height: 14px;
	line-height: 14px;
	padding: 1px 4px 1px 5px;
	position: absolute;
	right: 0;
	text-decoration: none;
	z-index: 10;
}

a.jx-knightlab div.knightlab-logo {
	display: inline-block;
	vertical-align: middle;
	height: 8px;
	width: 8px;
	background-color: #c34528;
	transform: rotate(45deg);
	-ms-transform: rotate(45deg);
	-webkit-transform: rotate(45deg);
	top: -1.25px;
	position: relative;
	cursor: pointer;
}

a.jx-knightlab:hover {
	background-color: #000; /* IE 8 */
	background-color: rgba(0,0,0,.35);
}
a.jx-knightlab:hover div.knightlab-logo {
	background-color: #ce4d28;
}

a.jx-knightlab span.juxtapose-name {
	display: table-cell;
	margin: 0;
	padding: 0;
	font-family: Helvetica, Arial, sans-serif;
	font-weight: 300;
	color: white;
	font-size: 10px;
	padding-left: 0.375em;
	vertical-align: middle;
	line-height: normal;
}

/* keyboard accessibility */
div.jx-controller:focus,
div.jx-image.jx-left div.jx-label:focus,
div.jx-image.jx-right div.jx-label:focus,
a.jx-knightlab:focus {
	background: #eae34a;
	color: #000;
}
a.jx-knightlab:focus span.juxtapose-name{
	color: #000;
	border: none;
}