├── LICENSE ├── test.html ├── README.md └── preview.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 ^_^肥仔John 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Preview by fsjohnhuang 7 | 13 | 14 | 15 |   16 |
17 |
18 |   19 |
20 |
21 | 26 | 27 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | preview v1.2.0 2 | ======= 3 | 4 | 纯浏览器端的图片预览组件
5 | 支持IE5.5~IE11、Chrome、FF、Safari和Opera
6 | 7 | **Usage**
8 | ```` 9 |   10 |
11 |   12 |
13 | 14 | 32 | ```` 33 | 34 | **v1.2.0**
35 | 1. Preview构造函数添加opts配置入参,具体配置项如下:
36 | `onlegal`,当路径后缀与file的accept属性值匹配时触发,返回true(默认值)时将预览图片,false则预览图片。
37 | `onillegal`,当路径后缀与file的accept属性值不匹配时触发,返回true时将预览图片,false(默认值)则预览图片。
38 | 2. 添加`Preview.defaults.onlegal=true`和`Preview.defaults.onillegal=false`默认配置项。
39 | 40 | **v1.1.0**
41 | 1. 图片预览实例添加`reset()`方法,用于重置组件;
42 | 2. `Preview构造函数`入参由原来的顺序设置fileEl,previewEl改为无序设置。 43 | 44 | **v.1.0**
45 | 全局重构 46 | 47 | **v.0.5**
48 | IE10+通过window.URL.createObjectURL替代FileReader,缩短Data URI Scheme长度从而提高性能。 49 | 50 | **v0.4**
51 | 新增上传文件MIME类型筛选。默认值为`image/*`,通过input的accept属性值设置。
52 | 53 | **v0.3**
54 | 修复FF3.0不支持`FileReader`的bug。
55 | 56 | **v0.2**
57 | 修复IE11下当`document.documentMode < 10`时无法预览图片的bug。
58 | 59 | **v0.1**
60 | 通过滤镜和`FileReader`实现图片预览功能。
61 | 62 | -------------------------------------------------------------------------------- /preview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author fsjohnuang 3 | * @version 1.2.0 4 | * @description 纯前端图片预览组件 5 | */ 6 | ;(function(exports){ 7 | // 工具 8 | var utils = {}; 9 | utils.trim = function(str){ 10 | return /^\s*(\S*)\s*$/.exec(str)[1]; 11 | }; 12 | utils.preset = function(fn){ 13 | var presetArgs = [].slice.call(arguments, 1); 14 | return function(){ 15 | fn.apply(null, presetArgs.concat([].slice.call(arguments))); 16 | }; 17 | }; 18 | utils.extend = function(){ 19 | var ret = {}; 20 | for (var i = 0, len = arguments.length, arg; i < len; ++i){ 21 | if (arg = arguments[i]){ 22 | for (var p in arg){ 23 | if (!ret.hasOwnProperty(p)){ 24 | ret[p] = arg[p]; 25 | } 26 | } 27 | } 28 | } 29 | return ret; 30 | }; 31 | 32 | /** 33 | 配置项 34 | **/ 35 | var imgCls = 'data-preview-img', 36 | FILTER_NAME= 'DXImageTransform.Microsoft.AlphaImageLoader', 37 | FILTER= ' progid:' + FILTER_NAME + '(sizingMethod="scale")'; 38 | var MIME_EXT_MAP = { 39 | 'jpeg': ['image/jpeg'], 40 | 'jpg': ['image/jpeg'], 41 | 'gif': ['image/gif'], 42 | 'png': ['image/png', 'image/x-png'], 43 | 'tiff': ['image/tiff'], 44 | 'bmp': ['image/x-ms-bmp', 'image/bmp'] 45 | }; 46 | 47 | /** 48 | 特征检测 49 | v1.0.1 修复document.body未生成时,特征检测报错的bug 50 | **/ 51 | var useFilter = !!(document.documentElement.filters && document.documentElement.filters.item); 52 | var isIE11 = document.documentMode === 11; 53 | 54 | /** 55 | 兼容性处理 56 | **/ 57 | var on, off; 58 | //v1.0.1 修复document.body未生成时,特征检测报错的bug 59 | if (document.documentElement.addEventListener){ 60 | on = function(el, evt, fn){ 61 | el.addEventListener(evt, fn); 62 | }; 63 | off = function(el, evt, fn){ 64 | el.removeEventListener.apply(el, Array.prototype.slice.call(arguments,1)); 65 | }; 66 | } 67 | else{ 68 | on = function(el, evt, fn){ 69 | el.attachEvent('on' + evt, fn); 70 | }; 71 | off = function(el, evt, fn){ 72 | var args = Array.prototype.slice.call(arguments,1); 73 | args[0] = 'on' + args[0]; 74 | el.detachEvent.apply(el, args); 75 | }; 76 | } 77 | var URL = (function(URL){ 78 | if (!URL) return; 79 | 80 | return { 81 | createObjectURL: function(blob){ 82 | return URL.createObjectURL(blob); 83 | }, 84 | revokeObjectURL: function(url){ 85 | URL.revokeObjectURL(url); 86 | } 87 | }; 88 | }(window.webkitURL || window.URL)); 89 | 90 | /** 91 | 主逻辑 92 | **/ 93 | /** 现代浏览器获取图片地址 94 | * @param {File} file 文件对象 95 | * @param {Function} cb({DOMString} dataURIScheme) 回调函数 96 | */ 97 | var readAsDataURL = function(file, cb/*({DOMString} dataURIScheme)*/){ 98 | // 空文件则返回空字符串 99 | if (!file) return cb(''); 100 | 101 | if (!!URL){ 102 | // 使用window.URL.createObjectURL提高性能 103 | cb(URL.createObjectURL(file)); 104 | } 105 | else if (!window.FileReader){ 106 | // ff3.0不支持FileReader 107 | cb(file.readAsDataURL()); 108 | } 109 | else{ 110 | var fr = new window.FileReader(); 111 | on(fr, 'load', function(e){ 112 | cb(e.target.result); 113 | }); 114 | fr.readAsDataURL(file); 115 | } 116 | }; 117 | /** IE10以下获取图片地址 118 | * @param {HTMLFileElement} fileEl 文件上传元素 119 | * @return {DOMString} url 120 | */ 121 | var readPath = function(fileEl){ 122 | var src = fileEl.value || ''; 123 | // IE11下,文档模式小于10无法通过value、getAttribute和outerHTML获取input[type=file]的真实路径 124 | if (src.search(/\w:\\fakepath/) === 0){ 125 | fileEl.select(); 126 | src = document.selection.createRangeCollection()[0].htmlText; 127 | } 128 | return src; 129 | }; 130 | /** 清理预览图的渲染 131 | * @param {HTMLElement} previewEl 预览图区域元素 132 | * @param {Boolean} [isRemove=false] 是否将预览图IMG元素从DOM树移除 133 | */ 134 | var clearRender = function(previewEl, isRemove){ 135 | var img = previewEl.getElementsByClassName(imgCls)[0]; 136 | if (!img) return null; 137 | 138 | // 释放window.URL.createObjectURL生成的链接所独占的资源 139 | URL && URL.revokeObjectURL(img.src); 140 | if (isRemove){ 141 | // IE10+只有removeNode没有remove方法 142 | img[img.remove && 'remove' || 'removeNode'].call(img); 143 | img = null; 144 | } 145 | 146 | return img; 147 | }; 148 | /** 检查文件后缀是否与file的accept属性值匹配,并触发事件 149 | * @param {PlainObject} _ Preview实例内置配置项 150 | * @param {DOCString} src 文件路径 151 | * @param {Function} isExpectedMIME 检查文件后缀的函数 152 | * @param {Boolean} 是否渲染预览图 153 | */ 154 | var isRender = function(_, src, isExpectedMIME){ 155 | var ext = ''; 156 | var lastFullStopIndex = src.lastIndexOf('.'); 157 | if (lastFullStopIndex > 0) 158 | ext = src.substring(lastFullStopIndex + 1); 159 | var ret = _.opts[isExpectedMIME(MIME_EXT_MAP[ext])?'onlegal':'onillegal'] 160 | .call(_.self, src, ext, _.accept); 161 | return ret; 162 | }; 163 | var render = function(){ 164 | render[useFilter ? 'legacy' : 'modern'].apply(null, arguments); 165 | }; 166 | /** 现代浏览器显示预览图 167 | * v1.0.2修复src为undefined或null时图片显示出错的bug 168 | * @param {PlainObject} _ Preview实例内置配置项 169 | * @param {DOMString} src 图片地址 170 | * @param {HTMLElement} previewEl 预览图元素 171 | * @param {Function} isExpectedMIME 文件后缀检测函数 172 | */ 173 | render['modern'] = function(_, src, previewEl, isExpectedMIME){ 174 | var img = clearRender(previewEl, !src); 175 | if (src){ 176 | if (isRender(_, _.fileEl.files[0].name, isExpectedMIME)){ 177 | if (!img){ 178 | img = new Image(); 179 | img.className = imgCls; 180 | img.style.width = previewEl.offsetWidth + 'px'; 181 | img.style.height = previewEl.offsetHeight + 'px'; 182 | previewEl.appendChild(img); 183 | } 184 | img.src = src; 185 | } 186 | } 187 | }; 188 | /** IE10以下显示预览图 189 | * @param {PlainObject} _ Preview实例内置配置项 190 | * @param {DOMString} src 图片地址 191 | * @param {HTMLElement} previewEl 预览图元素 192 | * @param {Function} isExpectedMIME 文件后缀检测函数 193 | */ 194 | render['legacy'] = function(_, src, previewEl, isExpectedMIME){ 195 | if (isRender(_, src, isExpectedMIME)) 196 | previewEl.filters.item(FILTER_NAME).src = src; 197 | } 198 | var exec= function(){ 199 | exec[useFilter ? 'legacy' : 'modern'].apply(null, arguments); 200 | }; 201 | /** 现代浏览器执行预览操作 202 | * @param {PlainObject} _ Preview实例内置配置项 203 | * @param {HTMLFileElement} fileEl 文件上传元素 204 | * @param {HTMLElement} previewEl 预览图元素 205 | * @param {Function} isExpectedMIME 文件后缀检测函数 206 | */ 207 | exec['modern'] = function(_, fileEl, previewEl, isExpectedMIME){ 208 | readAsDataURL(fileEl.files[0], 209 | utils.preset(function(_, url){ 210 | render(_, url, previewEl, isExpectedMIME); 211 | }, _)); 212 | }; 213 | /** IE10以下执行预览操作 214 | * @param {PlainObject} _ Preview实例内置配置项 215 | * @param {HTMLFileElement} fileEl 文件上传元素 216 | * @param {HTMLElement} previewEl 预览图元素 217 | * @param {Function} isExpectedMIME 文件后缀检测函数 218 | */ 219 | exec['legacy'] = function(_, fileEl, previewEl, isExpectedMIME){ 220 | var url = readPath(fileEl); 221 | render(_, url, previewEl, isExpectedMIME); 222 | }; 223 | /** 重置预览图渲染 224 | * @param {HTMLElement} 预览图区域元素 225 | */ 226 | var resetRender = function(previewEl){ 227 | if (useFilter){ 228 | // 滤镜AlphaImageLoader的src为无效路径时会抛出Error 229 | // 因此需要重置滤镜 230 | previewEl.style.filter = previewEl.style.filter.replace(FILTER,''); 231 | setTimeout(function(){ 232 | previewEl.style.filter += FILTER; 233 | }, 0); 234 | } 235 | else{ 236 | clearRender(previewEl, true); 237 | } 238 | }; 239 | var frm4Reset; 240 | /** 重置input[type=file]元素的value值 241 | * @param {HTMLFileElement} 文件上传元素 242 | */ 243 | var resetVal = function(fileEl){ 244 | if (document.documentMode && document.documentMode < 11){ 245 | // IE10及以下版本无法通过js修改value 246 | // 需要通过附加到非渲染的form元素实现重置 247 | var p = fileEl.parentNode, n = fileEl.nextSibling; 248 | frm4Reset = frm4Reset || document.createElement('form'); 249 | frm4Reset.appendChild(fileEl); 250 | frm4Reset.reset(); 251 | p.insertBefore(fileEl, n); 252 | } 253 | else{ 254 | fileEl.value = ''; 255 | } 256 | }; 257 | 258 | /** 构造图片预览组件 259 | * 参数的位置可互换 260 | * @param {HTMLFileElement} fileEl 文件上传元素 261 | * @param {HTMLElement} previewEl 预览区域元素,建议使用div 262 | * @param {PlainObject} opts 配置项 263 | */ 264 | var pv = exports.Preview = function(arg1, arg2, opts){ 265 | if (2 > arguments.length) throw Error("Failed to execute 'Preview': 2 over arguments required, but only " + arguments.length + " present"); 266 | if (!(this instanceof pv)) return new pv(arg1, arg2); 267 | 268 | // 私有属性, 建议使用者不要随便修改 269 | var _ = this._ = {}; 270 | _.self = this; 271 | for (var i = 0, arg; i < 2 && (arg = arguments[i++]);){ 272 | if (arg.nodeName === 'INPUT' && arg.type === 'file') 273 | _.fileEl = arg; 274 | else 275 | _.previewEl = arg; 276 | } 277 | if (!_.fileEl) throw Error("Failed to execute 'Preview': HTMLInputElement[type=file] required, but there is no one present"); 278 | 279 | _.opts = utils.extend(opts, pv.defaults); 280 | // 将onlegal和onillegal转为函数 281 | var origLegal, origIllegal; 282 | if (typeof _.opts.onlegal !== 'function'){ 283 | origLegal = _.opts.onlegal; 284 | _.opts.onlegal = function(){ 285 | return origLegal; 286 | }; 287 | } 288 | if (typeof _.opts.onillegal !== 'function'){ 289 | origIllegal = _.opts.onillegal; 290 | _.opts.onillegal = function(){ 291 | return origIllegal; 292 | }; 293 | } 294 | 295 | if (useFilter) 296 | _.previewEl.style.filter += FILTER; 297 | 298 | // v1.2.0文件后缀校验函数 299 | var isExpectedMIME = (function(accept){ 300 | // 正则化, 将形如image/*,image/jpg正则化为image/[^\\u002c]+,image/jpg 301 | accept = accept.replace(/\*/g, function(m){ 302 | return '[^\\u002c]+'; // 使用逗号的unicode字符编码,以便后面以逗号,作为分隔符 303 | }); 304 | var acceptMIMEs = accept.split(/\s*,\s*/) 305 | , rAcceptMIMEs = []; 306 | for (var i = 0, am; am = acceptMIMEs[i++];) 307 | rAcceptMIMEs.push(RegExp(am)); 308 | 309 | /* 310 | * @param {DOMString | Array} mimes input[type=file]元素上传文件的MIME类型 311 | */ 312 | return function(mimes){ 313 | mimes = [].concat(mimes); 314 | for (var i = 0, r; r = rAcceptMIMEs[i++];){ 315 | for (var j = 0, m; m = mimes[j++];){ 316 | if (r.test(m)) return true; 317 | } 318 | } 319 | return false; 320 | }; 321 | }(_.accept = _.fileEl.accept || 'image/*')); 322 | 323 | on(_.fileEl, 'change', function(){ 324 | exec(_, _.fileEl, _.previewEl, isExpectedMIME); 325 | }); 326 | }; 327 | 328 | /** 重置图片预览组件 329 | */ 330 | pv.prototype.reset = function(){ 331 | var _ = this._; 332 | resetVal(_.fileEl); 333 | // IE11修改fileEl.value后会触发change事件,因此不用重置渲染 334 | isIE11 || resetRender(_.previewEl); 335 | }; 336 | 337 | /** 默认配置 338 | */ 339 | pv.defaults = { 340 | onlegal: true, 341 | onillegal: false 342 | }; 343 | }(window)); --------------------------------------------------------------------------------