Skip to content

Instantly share code, notes, and snippets.

@davidbgk
Last active December 30, 2019 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidbgk/b455d4c3744737c30c6874e4cf8783a3 to your computer and use it in GitHub Desktop.
Save davidbgk/b455d4c3744737c30c6874e4cf8783a3 to your computer and use it in GitHub Desktop.
Upload images form with drag & drop and previews and progress upload
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang=fr>
<!-- Has to be within the first 1024 bytes, hence before the <title>
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset=utf-8>
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
<!-- Avoid indexation of the content as it is private. -->
<meta name="robots" content="noindex, nofollow">
<!-- Prevent access via referrers, only the domain will be available. -->
<meta content="origin-when-cross-origin" name="referrer">
<!-- Required to make a valid HTML5 document. -->
<title>Upload progress form</title>
<!-- Lightest blank gif, avoids an extra query to the server. -->
<link rel=icon href="data:;base64,iVBORw0KGgo=">
<style type="text/css">
form {
max-width: 30rem;
margin: 1rem auto;
font-size: 2rem;
text-align: center;
}
input[type="file"] {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
input[type="file"] + label {
font-size: 1.25em;
font-weight: 700;
color: white;
background-color: black;
display: inline-block;
}
input[type="file"]:focus + label,
input[type="file"] + label:hover {
background-color: red;
}
input[type="file"] + label {
cursor: pointer; /* "hand" cursor */
}
input[type="file"]:focus + label {
outline: thin dotted;
outline: -webkit-focus-ring-color auto 5px;
}
input[type="file"]:focus-within + label {
outline: thin dotted;
outline: -webkit-focus-ring-color auto 5px;
}
input[type="file"] + label * {
pointer-events: none;
}
output img {
max-width: 30rem;
}
output.oversized {
background: red;
height: 4rem;
padding: 1rem 2rem;
}
canvas {
border: 5px solid black;
margin: 2rem auto;
}
canvas.hidden {
display: none;
}
canvas:hover,
canvas.active {
border-color: red;
}
</style>
<form action="/" method="post" enctype="multipart/form-data">
<fieldset>
<input
type="file" name="files" id="files"
accept="image/*" multiple
data-max-size-kb="500">
<label for="files">
Click to upload image(s) or drag & drop below
</label>
<output></output>
<canvas></canvas>
</fieldset>
<input type="submit" name="submit">
</form>
<script type="text/javascript">
/*
Resources:
- https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
- https://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/
- https://eloquentjavascript.net/18_http.html
*/
;(function handleImageSelection(
inputSelector,
outputSelector,
dropzoneSelector
) {
'use strict'
const input = document.querySelector(inputSelector)
const output = document.querySelector(outputSelector)
const dropzone = document.querySelector(dropzoneSelector)
function handleImages(images) {
Array.from(images).forEach(image => {
const imageName = image.name
const imageSize = image.size
const isOversized =
Math.round(imageSize / 1024) > Number(input.dataset.maxSizeKb)
if (isOversized) {
output.classList.add('oversized')
output.innerText = `Oversized!`
input.value = ''
return
} else {
output.classList.remove('oversized')
dropzone.classList.add('hidden')
}
const figure = document.createElement('figure')
const img = document.createElement('img')
img.file = image // Required for future upload, fragile?
img.src = window.URL.createObjectURL(image)
img.onload = event => window.URL.revokeObjectURL(event.target.src)
img.alt = 'Picture preview'
figure.appendChild(img)
const figcaption = document.createElement('figcaption')
figcaption.innerText = imageName
figure.appendChild(figcaption)
output.appendChild(figure)
})
}
input.addEventListener(
'change',
event => {
event.stopPropagation()
event.preventDefault()
handleImages(event.target.files)
},
false
)
dropzone.addEventListener(
'drop',
event => {
event.stopPropagation()
event.preventDefault()
handleImages(event.dataTransfer.files)
},
false
)
})('input[type="file"]', 'output', 'canvas')
;(function uploadImageWithProgress(formSelector, imagesSelector) {
'use strict'
function request(url, options = {}, onProgress) {
// See https://github.com/github/fetch/issues/89#issuecomment-256610849
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(options.method || 'get', url)
for (let header in options.headers || {})
xhr.setRequestHeader(header, options.headers[header])
xhr.onload = event => resolve(event.target.responseText)
xhr.onerror = reject
if (xhr.upload && onProgress) xhr.upload.onprogress = onProgress
xhr.send(options.body)
})
}
function displayPercentage(event) {
if (event.lengthComputable) {
const percentage = Math.round((event.loaded * 100) / event.total)
console.log(percentage)
}
}
const form = document.querySelector(formSelector)
form.addEventListener(
'submit',
event => {
event.stopPropagation()
event.preventDefault()
const images = document.querySelectorAll(imagesSelector)
Array.from(images).forEach(image => {
const options = {
method: event.target.method,
body: image.file
}
console.log(`Uploading "${image.file.name}"`)
request(event.target.action, options, displayPercentage)
.then(console.log)
.catch(console.log.bind(console))
})
},
false
)
})('form', 'output img')
;(function highlightOnDragOrFocus(targetSelector, className) {
'use strict'
function addEventListenerMulti(element, events, fn) {
/* https://stackoverflow.com/a/45096932 */
events
.split(' ')
.forEach(event => element.addEventListener(event, fn, false))
}
const target = document.querySelector(targetSelector)
if (target) {
addEventListenerMulti(target, 'dragenter dragover focus', event => {
event.stopPropagation()
event.preventDefault()
target.classList.add(className)
})
addEventListenerMulti(target, 'dragleave blur', event => {
event.stopPropagation()
event.preventDefault()
target.classList.remove(className)
})
}
})('canvas', 'active')
;(function openFileSelectorOnDropzoneClick(targetSelector, inputSelector) {
'use strict'
const target = document.querySelector(targetSelector)
const input = document.querySelector(inputSelector)
target.addEventListener('click', event => input.click(), false)
})('canvas', 'input[type="file"]')
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment