├── 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));
--------------------------------------------------------------------------------