main.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <template>
  2. <div class="el-image">
  3. <slot v-if="loading" name="placeholder">
  4. <div class="el-image__placeholder"></div>
  5. </slot>
  6. <slot v-else-if="error" name="error">
  7. <div class="el-image__error">{{ t('el.image.error') }}</div>
  8. </slot>
  9. <img
  10. v-else
  11. class="el-image__inner"
  12. v-bind="$attrs"
  13. v-on="$listeners"
  14. @click="clickHandler"
  15. :src="src"
  16. :style="imageStyle"
  17. :class="{ 'el-image__inner--center': alignCenter, 'el-image__preview': preview }">
  18. <template v-if="preview">
  19. <image-viewer :z-index="zIndex" :initial-index="imageIndex" v-if="showViewer" :on-close="closeViewer" :url-list="previewSrcList"/>
  20. </template>
  21. </div>
  22. </template>
  23. <script>
  24. import ImageViewer from './image-viewer';
  25. import Locale from 'element-ui/src/mixins/locale';
  26. import { on, off, getScrollContainer, isInContainer } from 'element-ui/src/utils/dom';
  27. import { isString, isHtmlElement } from 'element-ui/src/utils/types';
  28. import throttle from 'throttle-debounce/throttle';
  29. const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined;
  30. const ObjectFit = {
  31. NONE: 'none',
  32. CONTAIN: 'contain',
  33. COVER: 'cover',
  34. FILL: 'fill',
  35. SCALE_DOWN: 'scale-down'
  36. };
  37. let prevOverflow = '';
  38. export default {
  39. name: 'ElImage',
  40. mixins: [Locale],
  41. inheritAttrs: false,
  42. components: {
  43. ImageViewer
  44. },
  45. props: {
  46. src: String,
  47. fit: String,
  48. lazy: Boolean,
  49. scrollContainer: {},
  50. previewSrcList: {
  51. type: Array,
  52. default: () => []
  53. },
  54. zIndex: {
  55. type: Number,
  56. default: 2000
  57. }
  58. },
  59. data() {
  60. return {
  61. loading: true,
  62. error: false,
  63. show: !this.lazy,
  64. imageWidth: 0,
  65. imageHeight: 0,
  66. showViewer: false
  67. };
  68. },
  69. computed: {
  70. imageStyle() {
  71. const { fit } = this;
  72. if (!this.$isServer && fit) {
  73. return isSupportObjectFit()
  74. ? { 'object-fit': fit }
  75. : this.getImageStyle(fit);
  76. }
  77. return {};
  78. },
  79. alignCenter() {
  80. return !this.$isServer && !isSupportObjectFit() && this.fit !== ObjectFit.FILL;
  81. },
  82. preview() {
  83. const { previewSrcList } = this;
  84. return Array.isArray(previewSrcList) && previewSrcList.length > 0;
  85. },
  86. imageIndex() {
  87. let previewIndex = 0;
  88. const srcIndex = this.previewSrcList.indexOf(this.src);
  89. if (srcIndex >= 0) {
  90. previewIndex = srcIndex;
  91. }
  92. return previewIndex;
  93. }
  94. },
  95. watch: {
  96. src(val) {
  97. this.show && this.loadImage();
  98. },
  99. show(val) {
  100. val && this.loadImage();
  101. }
  102. },
  103. mounted() {
  104. if (this.lazy) {
  105. this.addLazyLoadListener();
  106. } else {
  107. this.loadImage();
  108. }
  109. },
  110. beforeDestroy() {
  111. this.lazy && this.removeLazyLoadListener();
  112. },
  113. methods: {
  114. loadImage() {
  115. if (this.$isServer) return;
  116. // reset status
  117. this.loading = true;
  118. this.error = false;
  119. const img = new Image();
  120. img.onload = e => this.handleLoad(e, img);
  121. img.onerror = this.handleError.bind(this);
  122. // bind html attrs
  123. // so it can behave consistently
  124. Object.keys(this.$attrs)
  125. .forEach((key) => {
  126. const value = this.$attrs[key];
  127. img.setAttribute(key, value);
  128. });
  129. img.src = this.src;
  130. },
  131. handleLoad(e, img) {
  132. this.imageWidth = img.width;
  133. this.imageHeight = img.height;
  134. this.loading = false;
  135. this.error = false;
  136. },
  137. handleError(e) {
  138. this.loading = false;
  139. this.error = true;
  140. this.$emit('error', e);
  141. },
  142. handleLazyLoad() {
  143. if (isInContainer(this.$el, this._scrollContainer)) {
  144. this.show = true;
  145. this.removeLazyLoadListener();
  146. }
  147. },
  148. addLazyLoadListener() {
  149. if (this.$isServer) return;
  150. const { scrollContainer } = this;
  151. let _scrollContainer = null;
  152. if (isHtmlElement(scrollContainer)) {
  153. _scrollContainer = scrollContainer;
  154. } else if (isString(scrollContainer)) {
  155. _scrollContainer = document.querySelector(scrollContainer);
  156. } else {
  157. _scrollContainer = getScrollContainer(this.$el);
  158. }
  159. if (_scrollContainer) {
  160. this._scrollContainer = _scrollContainer;
  161. this._lazyLoadHandler = throttle(200, this.handleLazyLoad);
  162. on(_scrollContainer, 'scroll', this._lazyLoadHandler);
  163. this.handleLazyLoad();
  164. }
  165. },
  166. removeLazyLoadListener() {
  167. const { _scrollContainer, _lazyLoadHandler } = this;
  168. if (this.$isServer || !_scrollContainer || !_lazyLoadHandler) return;
  169. off(_scrollContainer, 'scroll', _lazyLoadHandler);
  170. this._scrollContainer = null;
  171. this._lazyLoadHandler = null;
  172. },
  173. /**
  174. * simulate object-fit behavior to compatible with IE11 and other browsers which not support object-fit
  175. */
  176. getImageStyle(fit) {
  177. const { imageWidth, imageHeight } = this;
  178. const {
  179. clientWidth: containerWidth,
  180. clientHeight: containerHeight
  181. } = this.$el;
  182. if (!imageWidth || !imageHeight || !containerWidth || !containerHeight) return {};
  183. const vertical = imageWidth / imageHeight < 1;
  184. if (fit === ObjectFit.SCALE_DOWN) {
  185. const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight;
  186. fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN;
  187. }
  188. switch (fit) {
  189. case ObjectFit.NONE:
  190. return { width: 'auto', height: 'auto' };
  191. case ObjectFit.CONTAIN:
  192. return vertical ? { width: 'auto' } : { height: 'auto' };
  193. case ObjectFit.COVER:
  194. return vertical ? { height: 'auto' } : { width: 'auto' };
  195. default:
  196. return {};
  197. }
  198. },
  199. clickHandler() {
  200. // don't show viewer when preview is false
  201. if (!this.preview) {
  202. return;
  203. }
  204. // prevent body scroll
  205. prevOverflow = document.body.style.overflow;
  206. document.body.style.overflow = 'hidden';
  207. this.showViewer = true;
  208. },
  209. closeViewer() {
  210. document.body.style.overflow = prevOverflow;
  211. this.showViewer = false;
  212. }
  213. }
  214. };
  215. </script>