jquery-photo-uploader
Version:
jQuery Plugin for uploading local photos and capturing from a webcam
632 lines (551 loc) • 15.2 kB
JavaScript
(function ($) {
var parameters = {};
var canvas = {};
var context = {};
var inputCache = [];
var camera = {
deviceIds: [],
constraints: {
video: {
width: parameters.photoWidth,
height: parameters.photoHeight,
frameRate: { ideal: 10, max: 10 }
}
}
};
var currentImage = {
image: {},
height: 0,
width: 0,
top: 0,
left: 0,
right: 0,
bottom: 0
};
var mouseEvents = {
offsetX: 0,
offsetY: 0,
startX: 0,
startY: 0,
isDragging: false,
prevDiff: 0
};
$.fn.PhotoUploader = function (userParameters) {
parameters = $.extend({}, $.fn.PhotoUploader.defaultParameters, userParameters);
canvas = $("<canvas>", {
id: "canvas",
style: "border: 1px solid black; touch-action: none"
})
.attr("width", parameters.photoWidth)
.attr("height", parameters.photoHeight)
.on('mousedown', handleMouseDown)
.on('touchstart', handleMouseDown);
$(document).on('mousemove', handleMouseMove)
.on('touchmove', handleMouseMove)
.on('touchend', handleMouseUp)
.on('mouseup', handleMouseUp);
canvasOffset = canvas.offset();
mouseEvents.offsetX = canvasOffset.left;
mouseEvents.offsetY = canvasOffset.top;
context = canvas.get(0).getContext("2d");
this.append(createModal());
$("#upload-image").on("hidden.bs.modal", function () {
stopVideo();
});
this.show = function () {
$("#upload-image").modal("show");
};
return this;
};
function createHeader() {
var modalHeader = $("<div>", {
class: "modal-header"
});
modalHeader.append(
$("<button>", {
type: "button",
class: "close",
"data-dismiss": "modal",
"aria-label": "Close"
}).append(
$("<span>", {
"aria-hidden": "true",
html: "×"
}))).append(
$("<h4>", {
class: "modal-title",
id: "upload-Image-label",
text: "Upload Photo"
}));
return modalHeader;
}
function createFileSelect() {
var fileSelect = $("<div>", {
class: function () {
if (canCapture()) {
return "col-md-6";
} else {
return "col-md-12"
}
},
id: "fileSelect"
}).append(
$("<input>", {
style: "display: none",
type: "file",
name: "file",
id: "file",
size: "50"
}).change(function (e) {
fileSelectChanged(e)
})
).append(
$("<label>", {
for: "file",
html: '<i class="fa fa-picture-o" aria-hidden="true"></i><br/>Upload an existing Photo'
})
).append(
$("<p>", {
text: "Max photo size: " + parameters.maxPhotoSize
})
);
return fileSelect;
}
function createCameraSelect() {
if (canCapture()) {
var cameraSelect = $("<div>", {
class: "col-md-6",
id: "cameraSelect"
}).append(
$("<label>", {
id: "captureFromWebcam",
html: '<i class="fa fa-video-camera" aria-hidden="true"></i><br>Capture from Webcam',
}).click(function () {
$("#previewPane").hide();
$("#capturePane").show();
$("#retake").show();
startVideo();
$("#snap").click(function () {
snapshotVideo();
})
})
);
return cameraSelect;
}
else {
return null;
}
}
function createCapturePane() {
if (!canCapture())
{
return null;
}
var capture = $("<div>", {
class: "row",
id: "capturePane",
style: "display:none; text-align: center"
}).append(
$("<video>", {
id: "video",
width: parameters.photoWidth,
height: parameters.photoHeight,
autoplay: true
})
).append(
$("<br>")
).append(
createCameraChooser()
).append(
$("<button>", {
class: "btn btn-primary",
type: "button",
id: "snap",
text: "Snap Photo"
})
);
return capture;
}
function createPreviewPane() {
var capture = $("<div>", {
class: "col-md-12",
id: "previewPane",
style: "display: none; text-align: center"
}).append(
canvas
).append(
$("<br>")
).append(
createEditControls()
).append(
$("<br>")
).append(
$("<button>", {
class: "btn btn-warning",
type: "button",
id: "retake",
style: "display:none",
text: "Re-Take Photo"
}).click(function () {
retakeSnapshot();
})
);
return capture;
}
function createCameraChooser() {
var cameraChooser = $("<button>", {
class: 'btn btn-default',
type: 'button',
id: 'switcher',
text: 'Switch Camera'
});
navigator.mediaDevices.enumerateDevices().then(function (devices) {
for (var i = 0; i < devices.length; i++) {
if (devices[i].kind !== 'videoinput') {
continue;
}
camera.deviceIds.push(devices[i].deviceId);
}
if (camera.deviceIds.length > 1) {
camera.selectedDevice = 0;
cameraChooser.on('click', function () {
if (camera.selectedDevice === camera.deviceIds.length - 1) {
camera.selectedDevice = 0;
} else {
camera.selectedDevice++;
}
camera.constraints.video.deviceId =
camera.deviceIds[camera.selectedDevice];
startVideo();
});
} else {
cameraChooser.hide();
}
});
return cameraChooser;
}
function createEditControls() {
var editControls = $("<div>", {
id: "editControls"
});
editControls.append(
$("<button>", {
class: "btn",
type: "button",
id: "shrink",
text: "-"
}).click(function () {
shrinkImage();
})
).append(
$("<button>", {
class: "btn",
type: "button",
id: "grow",
text: "+"
}).click(function () {
growImage();
})
);
return editControls;
}
function createBody() {
var modalBody = $("<div>", {
class: "modal-body"
});
var container = $("<div>", {
class: "container-fluid"
}).append(
$("<div>", {class: "row"}).append(
createFileSelect()
).append(
createCameraSelect()
)
).append(
$("<div>", {class: "row"}).append(
$("<div>", {id: "imageArea"})
.append(createCapturePane())
.append(createPreviewPane())
)
);
return modalBody.append(container);
}
function createFooter() {
var modalFooter = $("<div>", {
class: "modal-footer"
});
modalFooter.append(
$("<button>", {
type: "button",
class: "btn btn-default",
"data-dismiss": "modal",
text: "Close"
})
).append(
$("<input>", {
id: "uploadImage",
type: "submit",
class: "btn btn-primary",
"data-dismiss": "modal",
text: "Upload Image"
}).click(function (event) {
parameters.uploadImage(event);
})
);
return modalFooter;
}
function createModal() {
var modal = $("<div>", {
class: "modal fade",
id: "upload-image",
tabindex: "-1",
role: "dialog",
"aria-labelledby": "upload-Image-label",
"aria-hidden": "true"
});
var modalDialog = $("<div>", {
id: "photoUploader-dialog",
class: "modal-dialog modal-lg"
});
var uploadForm = $("<form>", {
action: "#",
method: "POST",
enctype: "multipart/form-data",
id: "UploadForm"
});
var modalContent = $("<div>", {
class: "modal-content"
});
return modal.append(
modalDialog.append(
uploadForm.append(
modalContent.append(
createHeader()
).append(
createBody()
).append(
createFooter()
)
)
)
);
}
function startVideo() {
$("#photoOr").show();
$("#photoCapture").show();
// Grab elements, create settings, etc.
this.video = document.getElementById('video');
if (parameters.fakeVideo) {
this.video.src = 'http://vjs.zencdn.net/v/oceans.mp4';
this.video.play();
return;
}
// Get access to the camera!
navigator.mediaDevices.getUserMedia(camera.constraints)
.then(function (userCameraStream) {
this.stream = userCameraStream;
this.video.src = window.URL.createObjectURL(userCameraStream);
this.video.play();
});
}
function stopVideo() {
if (this.stream) {
this.video.pause();
this.video.src = '';
this.stream.getTracks()[0].stop();
}
}
function retakeSnapshot() {
this.video.play();
$("#previewPane").hide();
$("#capturePane").show();
}
function snapshotVideo() {
this.video.pause();
currentImage.image = this.video;
currentImage.width = this.video.videoWidth;
currentImage.height = this.video.videoHeight;
fitImage();
calcEdges();
updateCanvas();
$("#capturePane").hide();
$("#previewPane").show();
}
function fileSelectChanged(fileSelect) {
var file = fileSelect.target.files[0];
fileSelect.target.files = null;
if (!file.type.match('image.*')) {
return;
}
$("#retake").hide();
$("#capturePane").hide();
stopVideo();
$("#previewPane").show();
currentImage.image = new Image();
currentImage.image.onload = function () {
currentImage.height = currentImage.image.height;
currentImage.width = currentImage.image.width;
fitImage();
calcEdges();
updateCanvas();
canvas.show();
};
currentImage.image.src = URL.createObjectURL(file);
}
function calcEdges() {
currentImage.right = currentImage.left + currentImage.width;
currentImage.bottom = currentImage.top + currentImage.height;
}
function fitImage() {
if (currentImage.width > parameters.photoWidth) {
//if the image is wider than the user asked for
ar = currentImage.height / currentImage.width;
currentImage.width = parameters.photoWidth;
currentImage.height = currentImage.width * ar;
}
else if (currentImage.height > parameters.photoHeight) {
//if the image is taller than the user asked for
ar = currentImage.width / currentImage.height;
currentImage.height = parameters.photoHeight;
currentImage.width = currentImage.height * ar;
}
}
function getInput(e) {
if (e.type === 'touchstart' || e.type === 'touchmove') {
var touches = [];
if (!e.touches) {
touches = e.originalEvent.touches;
} else {
touches = e.touches;
}
inputCache = [];
for (var i = 0; i < touches.length; i++) {
inputCache.push({
clientX: touches[i].clientX,
clientY: touches[i].clientY,
identifier: touches[i].identifier
});
}
if(inputCache.length == 2) {
// Calculate the distance between the two pointers
var diffX = inputCache[0].clientX - inputCache[1].clientX;
var diffY = inputCache[0].clientY - inputCache[1].clientY;
// Pythagorean theorem
mouseEvents.distance = Math.sqrt(diffX * diffX + diffY * diffY);
if(mouseEvents.isDragging) {
mouseEvents.prevDistance = mouseEvents.distance;
}
}
} else {
if (e.type === 'mousedown') {
inputCache.push({
clientX: e.clientX,
clientY: e.clientY,
identifier: 0
});
} else if (mouseEvents.isDragging) {
inputCache[0].clientX = e.clientX;
inputCache[0].clientY = e.clientY;
inputCache[0].identifier = 0;
}
}
}
function removeInput(e) {
if (e.type === 'mouseup') {
inputCache = [];
}
// Remove this event from the target's cache
for (var i = 0; i < inputCache.length; i++) {
if (inputCache[i].pointerId == e.pointerId) {
inputCache.splice(i, 1);
break;
}
}
}
function handleMouseUp(e) {
removeInput(e);
mouseEvents.isDragging = inputCache.length === 1;
}
function handleMouseDown(e) {
getInput(e);
mouseEvents.isDragging = inputCache.length === 1;
if (mouseEvents.isDragging) {
mouseEvents.startX = parseInt(inputCache[0].clientX - mouseEvents.offsetX);
mouseEvents.startY = parseInt(inputCache[0].clientY - mouseEvents.offsetY);
}
}
function handleMouseMove(e) {
getInput(e);
if (mouseEvents.isDragging) {
dX = parseInt(inputCache[0].clientX) - mouseEvents.startX - mouseEvents.offsetX;
dY = parseInt(inputCache[0].clientY) - mouseEvents.startY - mouseEvents.offsetY;
currentImage.top += dY;
currentImage.bottom += dY;
currentImage.left += dX;
currentImage.right += dX;
mouseEvents.startX = parseInt(inputCache[0].clientX);
mouseEvents.startY = parseInt(inputCache[0].clientY);
updateCanvas();
} else if (inputCache.length == 2) {
var scale = mouseEvents.distance / mouseEvents.prevDistance;
if (scale > 0) {
// The distance between the two pointers has decreased
currentImage.width *= scale;
currentImage.height *= scale;
calcEdges();
updateCanvas();
}
// Cache the distance for the next move event
mouseEvents.prevDistance = mouseEvents.distance;
}
}
function shrinkImage() {
currentImage.width *= .90;
currentImage.height *= .90;
calcEdges();
updateCanvas();
}
function growImage() {
currentImage.width *= 1.1;
currentImage.height *= 1.1;
calcEdges();
updateCanvas();
}
function updateCanvas() {
context.clearRect(0, 0, parameters.photoWidth, parameters.photoHeight);
context.drawImage(currentImage.image, currentImage.left, currentImage.top,
currentImage.width, currentImage.height);
context.beginPath();
context.moveTo(currentImage.left, currentImage.top);
context.lineTo(currentImage.right, currentImage.top);
context.lineTo(currentImage.right, currentImage.bottom);
context.lineTo(currentImage.left, currentImage.bottom);
context.closePath();
context.stroke();
}
function canCapture() {
return navigator.mediaDevices && navigator.mediaDevices.enumerateDevices && navigator.mediaDevices.getUserMedia && window.location.protocol == "https:"
}
$.fn.PhotoUploader.defaultParameters = {
url: "/photo",
maxPhotoSize: "2MB",
photoHeight: 240,
photoWidth: 320,
uploadImage: function (event) {
event.preventDefault();
var dataURL = canvas.get(0).toDataURL();
$.ajax({
method: "POST",
url: parameters.url,
data: {
imgBase64: dataURL
}
}).done(function (o) {
$("#upload-image").modal("hide");
if (parameters.done) {
parameters.done(o);
}
});
}
}
})(jQuery);