UNPKG

8.24 kBPlain TextView Raw
1<template>
2 <transition name="viewer-fade">
3 <div tabindex="-1" ref="el-image-viewer__wrapper" class="el-image-viewer__wrapper" :style="{ 'z-index': zIndex }">
4 <div class="el-image-viewer__mask"></div>
5 <!-- CLOSE -->
6 <span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
7 <i class="el-icon-circle-close"></i>
8 </span>
9 <!-- ARROW -->
10 <template v-if="!isSingle">
11 <span
12 class="el-image-viewer__btn el-image-viewer__prev"
13 :class="{ 'is-disabled': !infinite && isFirst }"
14 @click="prev">
15 <i class="el-icon-arrow-left"/>
16 </span>
17 <span
18 class="el-image-viewer__btn el-image-viewer__next"
19 :class="{ 'is-disabled': !infinite && isLast }"
20 @click="next">
21 <i class="el-icon-arrow-right"/>
22 </span>
23 </template>
24 <!-- ACTIONS -->
25 <div class="el-image-viewer__btn el-image-viewer__actions">
26 <div class="el-image-viewer__actions__inner">
27 <i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
28 <i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
29 <i class="el-image-viewer__actions__divider"></i>
30 <i :class="mode.icon" @click="toggleMode"></i>
31 <i class="el-image-viewer__actions__divider"></i>
32 <i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
33 <i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
34 </div>
35 </div>
36 <!-- CANVAS -->
37 <div class="el-image-viewer__canvas">
38 <img
39 v-for="(url, i) in urlList"
40 v-if="i === index"
41 ref="img"
42 class="el-image-viewer__img"
43 :key="url"
44 :src="currentImg"
45 :style="imgStyle"
46 @load="handleImgLoad"
47 @error="handleImgError"
48 @mousedown="handleMouseDown">
49 </div>
50 </div>
51 </transition>
52</template>
53
54<script>
55import { on, off } from 'element-ui/src/utils/dom';
56import { rafThrottle, isFirefox } from 'element-ui/src/utils/util';
57
58const Mode = {
59 CONTAIN: {
60 name: 'contain',
61 icon: 'el-icon-full-screen'
62 },
63 ORIGINAL: {
64 name: 'original',
65 icon: 'el-icon-c-scale-to-original'
66 }
67};
68
69const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
70
71export default {
72 name: 'elImageViewer',
73
74 props: {
75 urlList: {
76 type: Array,
77 default: () => []
78 },
79 zIndex: {
80 type: Number,
81 default: 2000
82 },
83 onSwitch: {
84 type: Function,
85 default: () => {}
86 },
87 onClose: {
88 type: Function,
89 default: () => {}
90 },
91 initialIndex: {
92 type: Number,
93 default: 0
94 }
95 },
96
97 data() {
98 return {
99 index: this.initialIndex,
100 isShow: false,
101 infinite: true,
102 loading: false,
103 mode: Mode.CONTAIN,
104 transform: {
105 scale: 1,
106 deg: 0,
107 offsetX: 0,
108 offsetY: 0,
109 enableTransition: false
110 }
111 };
112 },
113 computed: {
114 isSingle() {
115 return this.urlList.length <= 1;
116 },
117 isFirst() {
118 return this.index === 0;
119 },
120 isLast() {
121 return this.index === this.urlList.length - 1;
122 },
123 currentImg() {
124 return this.urlList[this.index];
125 },
126 imgStyle() {
127 const { scale, deg, offsetX, offsetY, enableTransition } = this.transform;
128 const style = {
129 transform: `scale(${scale}) rotate(${deg}deg)`,
130 transition: enableTransition ? 'transform .3s' : '',
131 'margin-left': `${offsetX}px`,
132 'margin-top': `${offsetY}px`
133 };
134 if (this.mode === Mode.CONTAIN) {
135 style.maxWidth = style.maxHeight = '100%';
136 }
137 return style;
138 }
139 },
140 watch: {
141 index: {
142 handler: function(val) {
143 this.reset();
144 this.onSwitch(val);
145 }
146 },
147 currentImg(val) {
148 this.$nextTick(_ => {
149 const $img = this.$refs.img[0];
150 if (!$img.complete) {
151 this.loading = true;
152 }
153 });
154 }
155 },
156 methods: {
157 hide() {
158 this.deviceSupportUninstall();
159 this.onClose();
160 },
161 deviceSupportInstall() {
162 this._keyDownHandler = rafThrottle(e => {
163 const keyCode = e.keyCode;
164 switch (keyCode) {
165 // ESC
166 case 27:
167 this.hide();
168 break;
169 // SPACE
170 case 32:
171 this.toggleMode();
172 break;
173 // LEFT_ARROW
174 case 37:
175 this.prev();
176 break;
177 // UP_ARROW
178 case 38:
179 this.handleActions('zoomIn');
180 break;
181 // RIGHT_ARROW
182 case 39:
183 this.next();
184 break;
185 // DOWN_ARROW
186 case 40:
187 this.handleActions('zoomOut');
188 break;
189 }
190 });
191 this._mouseWheelHandler = rafThrottle(e => {
192 const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
193 if (delta > 0) {
194 this.handleActions('zoomIn', {
195 zoomRate: 0.015,
196 enableTransition: false
197 });
198 } else {
199 this.handleActions('zoomOut', {
200 zoomRate: 0.015,
201 enableTransition: false
202 });
203 }
204 });
205 on(document, 'keydown', this._keyDownHandler);
206 on(document, mousewheelEventName, this._mouseWheelHandler);
207 },
208 deviceSupportUninstall() {
209 off(document, 'keydown', this._keyDownHandler);
210 off(document, mousewheelEventName, this._mouseWheelHandler);
211 this._keyDownHandler = null;
212 this._mouseWheelHandler = null;
213 },
214 handleImgLoad(e) {
215 this.loading = false;
216 },
217 handleImgError(e) {
218 this.loading = false;
219 e.target.alt = '加载失败';
220 },
221 handleMouseDown(e) {
222 if (this.loading || e.button !== 0) return;
223
224 const { offsetX, offsetY } = this.transform;
225 const startX = e.pageX;
226 const startY = e.pageY;
227 this._dragHandler = rafThrottle(ev => {
228 this.transform.offsetX = offsetX + ev.pageX - startX;
229 this.transform.offsetY = offsetY + ev.pageY - startY;
230 });
231 on(document, 'mousemove', this._dragHandler);
232 on(document, 'mouseup', ev => {
233 off(document, 'mousemove', this._dragHandler);
234 });
235
236 e.preventDefault();
237 },
238 reset() {
239 this.transform = {
240 scale: 1,
241 deg: 0,
242 offsetX: 0,
243 offsetY: 0,
244 enableTransition: false
245 };
246 },
247 toggleMode() {
248 if (this.loading) return;
249
250 const modeNames = Object.keys(Mode);
251 const modeValues = Object.values(Mode);
252 const index = modeValues.indexOf(this.mode);
253 const nextIndex = (index + 1) % modeNames.length;
254 this.mode = Mode[modeNames[nextIndex]];
255 this.reset();
256 },
257 prev() {
258 if (this.isFirst && !this.infinite) return;
259 const len = this.urlList.length;
260 this.index = (this.index - 1 + len) % len;
261 },
262 next() {
263 if (this.isLast && !this.infinite) return;
264 const len = this.urlList.length;
265 this.index = (this.index + 1) % len;
266 },
267 handleActions(action, options = {}) {
268 if (this.loading) return;
269 const { zoomRate, rotateDeg, enableTransition } = {
270 zoomRate: 0.2,
271 rotateDeg: 90,
272 enableTransition: true,
273 ...options
274 };
275 const { transform } = this;
276 switch (action) {
277 case 'zoomOut':
278 if (transform.scale > 0.2) {
279 transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
280 }
281 break;
282 case 'zoomIn':
283 transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
284 break;
285 case 'clocelise':
286 transform.deg += rotateDeg;
287 break;
288 case 'anticlocelise':
289 transform.deg -= rotateDeg;
290 break;
291 }
292 transform.enableTransition = enableTransition;
293 }
294 },
295 mounted() {
296 this.deviceSupportInstall();
297 // add tabindex then wrapper can be focusable via Javascript
298 // focus wrapper so arrow key can't cause inner scroll behavior underneath
299 this.$refs['el-image-viewer__wrapper'].focus();
300 }
301};
302</script>