├── LICENSE.txt ├── README.md ├── avatar.css ├── avatar.js ├── demo.html ├── placekitten.jpg └── usage.html /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Dan Harper, http://danharper.me 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Avatar Upload 2 | 3 | by [Dan Harper](http://danharper.me) 4 | 5 | ## Usage 6 | 7 | ### 1. Include Files 8 | Include the `avatar.js` and `avatar.css` files in your document. 9 | 10 | ### 2. Wrap a Replacable Image 11 | Insert the current image (or a placeholder) in the page, inside a div with a custom class (eg. `avatar-upload`): 12 | 13 | ```html 14 |
15 | 16 |
17 | ``` 18 | 19 | ### 3. Set your max images widths 20 | You must set a max width, otherwise images will fill the container. eg. 21 | 22 | ```css 23 | .avatar-upload { 24 | width: 200px; 25 | height: 200px; 26 | } 27 | 28 | .avatar-upload img { 29 | max-width: 200px; 30 | max-height: 200px; 31 | } 32 | ``` 33 | 34 | ### 4. Boot the module 35 | Now boot the module, providing it with the element containing the image to replace & the URL to upload to. 36 | 37 | ```js 38 | new AvatarUpload({ 39 | el: document.querySelector('.avatar-upload'), 40 | uploadUrl: '' 41 | }); 42 | ``` 43 | 44 | This will swap the contents of your `.avatar-upload` element with the uploader's markup. The image currently set won't appear any different. But you can now click the image to swap it out! 45 | 46 | You can optionally provide the following configuration settings to the module: 47 | 48 | * `el` **[required]** The element containing the current replaceable image 49 | * `uploadUrl` **[required]** Where to sent the new image to 50 | * `uploadMethod` The HTTP method to use when uploading (defaults to `POST`) 51 | * `uploadData` An object of additional data to send with the upload 52 | * `onProgress` A function to be called continuously as the upload progresses - takes one argument, the current upload percentage completion 53 | * `onSuccess` A function to be called when the upload completes successfully - takes one argument, the response from the server 54 | * `onError` A function to be called when the upload fails - takes one argument, the error response from the server 55 | * `pretentUpload` If `true`, no upload will be performed, it will be faked through a `setInterval`, pretending to upload - useful for testing initial set up 56 | 57 | ## Credit 58 | 59 | Kitten image in demo courtesy of [Paul Reynolds](http://www.flickr.com/photos/bigtallguy/148771151/) via [placekitten](http://placekitten.com/) 60 | -------------------------------------------------------------------------------- /avatar.css: -------------------------------------------------------------------------------- 1 | /*avatar upload module*/ 2 | .avatar-upload__shell { 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | /*module wrapper*/ 9 | .avatar-upload__wrapper { 10 | position: relative; 11 | overflow: hidden; 12 | width: 100%; 13 | height: 100%; 14 | } 15 | 16 | /*input element positioned over module, transparent*/ 17 | .avatar-upload__wrapper input { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | bottom: 0; 22 | right: 0; 23 | opacity: 0; 24 | } 25 | 26 | /*wraps the "real" image. width is increased during upload progress*/ 27 | .avatar-upload__image-wrapper { 28 | position: absolute; 29 | top: 0; 30 | bottom: 0; 31 | left: 0; 32 | right: 0; 33 | overflow: hidden; 34 | width: 0%; 35 | } 36 | 37 | /*general image positioning*/ 38 | .avatar-upload__wrapper img { 39 | position: absolute; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | 44 | /*"real" image width auto so it doesn't resize as wrapper width increases on progress*/ 45 | .avatar-upload__image-wrapper img { 46 | width: auto; 47 | } 48 | 49 | /*progress text is vertically aligned in the middle*/ 50 | .avatar-upload__progress-wrapper { 51 | position: relative; 52 | top: 50%; 53 | -webkit-transform: translateY(-50%); 54 | -ms-transform: translateY(-50%); 55 | transform: translateY(-50%); 56 | z-index: 1; 57 | width: 100%; 58 | text-align: center; 59 | font-size: 2.2em; 60 | color: white; 61 | text-shadow: 1px 1px rgba(0,0,0,.7); 62 | } 63 | 64 | /*the "faded" image appears in the background of the upload, becoming covered by real image*/ 65 | .avatar-upload__faded-image { 66 | opacity: .3; 67 | } 68 | 69 | /*when no upload is in progress, the "real" image should be fully visible*/ 70 | .avatar-upload--complete .avatar-upload__image-wrapper { 71 | width: 100%; 72 | } 73 | 74 | /*when no upload is in progress, progress is hidden*/ 75 | .avatar-upload--complete .avatar-upload__progress-wrapper { 76 | display: none; 77 | } 78 | 79 | /*when no upload is in progress, the "faded" image is hidden*/ 80 | .avatar-upload--complete .avatar-upload__faded-image { 81 | display: none; 82 | } 83 | -------------------------------------------------------------------------------- /avatar.js: -------------------------------------------------------------------------------- 1 | (function ( root, factory ) { 2 | if ( typeof define === 'function' && define.amd ) { 3 | // AMD. Register as an anonymous module. 4 | define(function () { 5 | // Also create a global in case some scripts 6 | // that are loaded still are looking for 7 | // a global even when an AMD loader is in use. 8 | return ( root.AvatarUpload = factory() ); 9 | }); 10 | } else { 11 | // Browser globals 12 | root.AvatarUpload = factory(); 13 | } 14 | }( this, function () { 15 | 16 | var extend = function(original, extra) { 17 | return Object.keys(extra).forEach(function(key) { 18 | original[key] = extra[key]; 19 | }); 20 | }; 21 | 22 | var parseJson = function(input) { 23 | try { 24 | return JSON.parse(input); 25 | } 26 | catch (e) { 27 | return false; 28 | } 29 | }; 30 | 31 | var AvatarUpload = function(config) { 32 | extend(this.config, config); 33 | 34 | if ( ! this.config.el) { 35 | throw new Error('An element is required to manipulate'); 36 | } 37 | 38 | if ( ! this.config.uploadUrl && ! this.config.pretendUpload) { 39 | throw new Error('Upload URL not specified'); 40 | } 41 | 42 | this.el = this.config.el; 43 | this.renderInput(); 44 | this.bindInput(); 45 | 46 | this.progressText = this.el.querySelector('span'); 47 | this.imageWrapper = this.el.querySelector('.avatar-upload__image-wrapper'); 48 | }; 49 | 50 | AvatarUpload.prototype.config = { 51 | el: undefined, 52 | 53 | uploadUrl: '', 54 | uploadMethod: 'post', 55 | uploadImageKey: 'upload', 56 | uploadData: {}, 57 | 58 | pretendUpload: false, 59 | 60 | onProgress: undefined, 61 | onSuccess: undefined, 62 | onError: undefined 63 | }; 64 | 65 | AvatarUpload.prototype.renderInput = function() { 66 | var imgEl = this.el.querySelector('img'), 67 | img = imgEl.src; 68 | 69 | var el = document.createElement('div'); 70 | el.className = 'avatar-upload__shell'; 71 | 72 | el.innerHTML = [ 73 | '
', 74 | '
', 75 | '', 76 | '
', 77 | '', 78 | '
', 79 | '0%', 80 | '
', 81 | '', 82 | '
', 83 | ].join(''); 84 | 85 | imgEl.parentNode.removeChild(imgEl); 86 | this.el.appendChild(el); 87 | 88 | return this; 89 | }; 90 | 91 | AvatarUpload.prototype.bindInput = function(event) { 92 | this.el.querySelector('input').addEventListener( 93 | 'change', this.initiateUpload.bind(this), true 94 | ); 95 | }; 96 | 97 | AvatarUpload.prototype.initiateUpload = function(event) { 98 | var file = event.target.files[0]; 99 | 100 | // reset input to allow selecting same file again 101 | event.target.value = null; 102 | 103 | if ( ! file.type.match(/image.*/)) return; 104 | 105 | // read file & run upload 106 | var reader = new FileReader; 107 | reader.onload = this.displayUpload.bind(this); 108 | reader.readAsDataURL(file); 109 | 110 | this.upload(file); 111 | }; 112 | 113 | AvatarUpload.prototype.displayUpload = function(event) { 114 | var img = event.target.result; 115 | 116 | this.uiUploadStart(img); 117 | }; 118 | 119 | AvatarUpload.prototype.upload = function(file) { 120 | var Uploader = this.config.pretendUpload ? FakeUploader : XhrUploader; 121 | 122 | Uploader(file, this.config, { 123 | progress: this.uploadProgress.bind(this), 124 | success: this.uploadSuccess.bind(this), 125 | error: this.uploadError.bind(this), 126 | }); 127 | }; 128 | 129 | AvatarUpload.prototype.uploadProgress = function(progress) { 130 | this.uiUploadProgress(progress); 131 | if (this.config.onProgress) this.config.onProgress(progress, this.el, this); 132 | }; 133 | 134 | AvatarUpload.prototype.uploadSuccess = function(xhr, json) { 135 | this.uiUploadSuccess(xhr, json); 136 | if (this.config.onSuccess) this.config.onSuccess(xhr, json); 137 | }; 138 | 139 | AvatarUpload.prototype.uploadError = function(xhr, json) { 140 | this.uiUploadError(xhr, json); 141 | if (this.config.onError) this.config.onError(xhr, json); 142 | }; 143 | 144 | AvatarUpload.prototype.uiUploadStart = function(img) { 145 | var origSrc; 146 | 147 | Array.prototype.forEach.call(this.el.querySelectorAll('img'), function(imgEl) { 148 | origSrc = imgEl.src; 149 | imgEl.src = img; 150 | }); 151 | 152 | this.origSrc = origSrc; 153 | 154 | this.el.querySelector('.avatar-upload__wrapper').className = 'avatar-upload__wrapper'; // remove complete class 155 | }; 156 | 157 | AvatarUpload.prototype.uiUploadProgress = function(progress) { 158 | this.progressText.textContent = progress+'%'; 159 | this.imageWrapper.style.width = progress+'%'; 160 | }; 161 | 162 | AvatarUpload.prototype.uiUploadSuccess = function(xhr, json) { 163 | this.progressText.textContent = '0%'; 164 | this.imageWrapper.style.width = null; 165 | this.el.querySelector('.avatar-upload__wrapper').className = 'avatar-upload__wrapper avatar-upload--complete'; 166 | }; 167 | 168 | AvatarUpload.prototype.uiUploadError = function(xhr, json) { 169 | this.uiUploadSuccess(); 170 | 171 | var origSrc = this.origSrc; 172 | Array.prototype.forEach.call(this.el.querySelectorAll('img'), function(imgEl) { 173 | imgEl.src = origSrc; 174 | }); 175 | }; 176 | 177 | var FakeUploader = function(file, config, callbacks) { 178 | var progress = 0; 179 | var id = setInterval(function() { 180 | progress += 1; 181 | // if (progress == 50) { 182 | // callbacks.error(); 183 | // return clearInterval(id); 184 | // } 185 | if (progress > 100) { 186 | callbacks.success(); 187 | return clearInterval(id); 188 | } 189 | callbacks.progress(progress); 190 | }, 50); 191 | }; 192 | 193 | var XhrUploader = function(file, config, callbacks) { 194 | var xhr = new XMLHttpRequest(), 195 | formData = new FormData(); 196 | 197 | xhr.upload.addEventListener('progress', function(transfer) { 198 | callbacks.progress(parseInt( 199 | transfer.loaded / transfer.total * 100 200 | , 10)); 201 | }); 202 | 203 | xhr.onreadystatechange = function(e) { 204 | if (xhr.readyState !== 4) return; 205 | 206 | if (xhr.status === 200) { 207 | callbacks.success(xhr, parseJson(xhr.response)); 208 | } 209 | else { 210 | callbacks.error(xhr, parseJson(xhr.response)); 211 | } 212 | }; 213 | 214 | formData.append(config.uploadImageKey, file); 215 | 216 | for (val in config.uploadData) { 217 | formData.append(val, config.uploadData[val]); 218 | } 219 | 220 | xhr.open(config.uploadMethod, config.uploadUrl); 221 | xhr.send(formData); 222 | }; 223 | 224 | return AvatarUpload; 225 | 226 | })); 227 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Avatar Uploader 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 |

Click image to replace.

29 | 30 |

Grab the Source! by Dan Harper.

31 | 32 | 33 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /placekitten.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danharper/avatar-uploader/62292236e109065b3e9895de5750d0430d4393e1/placekitten.jpg -------------------------------------------------------------------------------- /usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Avatar Uploader 6 | 7 | 8 | 9 | 45 | 46 | 47 | 48 |

Avatar Uploader

49 | 50 |

Grab the Source! by Dan Harper.

51 | 52 |
53 |
54 |

Example

55 |

Click the image to replace it.

56 |
57 |
58 | 59 |
60 |
61 | 62 |
63 |

Usage

64 | 65 |

1. Include Files

66 |

Include the avatar.js and avatar.css files in your document.

67 | 68 |

2. Wrap a Replacable Image

69 |

Insert the current image (or a placeholder) in the page, inside a div with a custom class (eg. `avatar-upload`):

70 |
<div class="avatar-upload">
 71 |     <img src="placekitten.jpg">
 72 | </div>
73 | 74 |

3. Set your max images widths

75 |

You must set a max width, otherwise images will fill the container. eg.

76 |

 77 | .avatar-upload {
 78 |     width: 200px;
 79 |     height: 200px;
 80 | }
 81 | 
 82 | .avatar-upload img {
 83 |     max-width: 200px;
 84 |     max-height: 200px;
 85 | }
86 | 87 |

4. Boot the module

88 |

Now boot the module, providing it with the element containing the image to replace & the URL to upload to.

89 |
<script>
 90 |     new AvatarUpload({
 91 |         el: document.querySelector('.avatar-upload'),
 92 |         uploadUrl: ''
 93 |     });
 94 | </script>
95 |

This will swap the contents of your .avatar-upload element with the uploader's markup. The image currently set won't appear any different. But you can now click the image to swap it out!

96 |

You can optionally provide the following configuration settings to the module:

97 | 107 | 108 |
109 | 110 | 111 | 122 | 123 | 124 | --------------------------------------------------------------------------------