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 |
|
6 | <span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
|
7 | <i class="el-icon-circle-close"></i>
|
8 | </span>
|
9 |
|
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 |
|
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 |
|
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>
|
55 | import { on, off } from 'element-ui/src/utils/dom';
|
56 | import { rafThrottle, isFirefox } from 'element-ui/src/utils/util';
|
57 |
|
58 | const 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 |
|
69 | const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
|
70 |
|
71 | export 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 |
|
166 | case 27:
|
167 | this.hide();
|
168 | break;
|
169 |
|
170 | case 32:
|
171 | this.toggleMode();
|
172 | break;
|
173 |
|
174 | case 37:
|
175 | this.prev();
|
176 | break;
|
177 |
|
178 | case 38:
|
179 | this.handleActions('zoomIn');
|
180 | break;
|
181 |
|
182 | case 39:
|
183 | this.next();
|
184 | break;
|
185 |
|
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 |
|
298 |
|
299 | this.$refs['el-image-viewer__wrapper'].focus();
|
300 | }
|
301 | };
|
302 | </script>
|