main.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. initialIndex: Number
  59. },
  60. data() {
  61. return {
  62. loading: true,
  63. error: false,
  64. show: !this.lazy,
  65. imageWidth: 0,
  66. imageHeight: 0,
  67. showViewer: false
  68. };
  69. },
  70. computed: {
  71. imageStyle() {
  72. const { fit } = this;
  73. if (!this.$isServer && fit) {
  74. return isSupportObjectFit()
  75. ? { 'object-fit': fit }
  76. : this.getImageStyle(fit);
  77. }
  78. return {};
  79. },
  80. alignCenter() {
  81. return !this.$isServer && !isSupportObjectFit() && this.fit !== ObjectFit.FILL;
  82. },
  83. preview() {
  84. const { previewSrcList } = this;
  85. return Array.isArray(previewSrcList) && previewSrcList.length > 0;
  86. },
  87. imageIndex() {
  88. let previewIndex = 0;
  89. const initialIndex = this.initialIndex;
  90. if (initialIndex >= 0) {
  91. previewIndex = initialIndex;
  92. return previewIndex;
  93. }
  94. const srcIndex = this.previewSrcList.indexOf(this.src);
  95. if (srcIndex >= 0) {
  96. previewIndex = srcIndex;
  97. return previewIndex;
  98. }
  99. return previewIndex;
  100. }
  101. },
  102. watch: {
  103. src(val) {
  104. this.show && this.loadImage();
  105. },
  106. show(val) {
  107. val && this.loadImage();
  108. }
  109. },
  110. mounted() {
  111. if (this.lazy) {
  112. this.addLazyLoadListener();
  113. } else {
  114. this.loadImage();
  115. }
  116. },
  117. beforeDestroy() {
  118. this.lazy && this.removeLazyLoadListener();
  119. },
  120. methods: {
  121. loadImage() {
  122. if (this.$isServer) return;
  123. // reset status
  124. this.loading = true;
  125. this.error = false;
  126. const img = new Image();
  127. img.onload = e => this.handleLoad(e, img);
  128. img.onerror = this.handleError.bind(this);
  129. // bind html attrs
  130. // so it can behave consistently
  131. Object.keys(this.$attrs)
  132. .forEach((key) => {
  133. const value = this.$attrs[key];
  134. img.setAttribute(key, value);
  135. });
  136. img.src = this.src;
  137. },
  138. handleLoad(e, img) {
  139. this.imageWidth = img.width;
  140. this.imageHeight = img.height;
  141. this.loading = false;
  142. this.error = false;
  143. },
  144. handleError(e) {
  145. this.loading = false;
  146. this.error = true;
  147. this.$emit('error', e);
  148. },
  149. handleLazyLoad() {
  150. if (isInContainer(this.$el, this._scrollContainer)) {
  151. this.show = true;
  152. this.removeLazyLoadListener();
  153. }
  154. },
  155. addLazyLoadListener() {
  156. if (this.$isServer) return;
  157. const { scrollContainer } = this;
  158. let _scrollContainer = null;
  159. if (isHtmlElement(scrollContainer)) {
  160. _scrollContainer = scrollContainer;
  161. } else if (isString(scrollContainer)) {
  162. _scrollContainer = document.querySelector(scrollContainer);
  163. } else {
  164. _scrollContainer = getScrollContainer(this.$el);
  165. }
  166. if (_scrollContainer) {
  167. this._scrollContainer = _scrollContainer;
  168. this._lazyLoadHandler = throttle(200, this.handleLazyLoad);
  169. on(_scrollContainer, 'scroll', this._lazyLoadHandler);
  170. this.handleLazyLoad();
  171. }
  172. },
  173. removeLazyLoadListener() {
  174. const { _scrollContainer, _lazyLoadHandler } = this;
  175. if (this.$isServer || !_scrollContainer || !_lazyLoadHandler) return;
  176. off(_scrollContainer, 'scroll', _lazyLoadHandler);
  177. this._scrollContainer = null;
  178. this._lazyLoadHandler = null;
  179. },
  180. /**
  181. * simulate object-fit behavior to compatible with IE11 and other browsers which not support object-fit
  182. */
  183. getImageStyle(fit) {
  184. const { imageWidth, imageHeight } = this;
  185. const {
  186. clientWidth: containerWidth,
  187. clientHeight: containerHeight
  188. } = this.$el;
  189. if (!imageWidth || !imageHeight || !containerWidth || !containerHeight) return {};
  190. const imageAspectRatio = imageWidth / imageHeight;
  191. const containerAspectRatio = containerWidth / containerHeight;
  192. if (fit === ObjectFit.SCALE_DOWN) {
  193. const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight;
  194. fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN;
  195. }
  196. switch (fit) {
  197. case ObjectFit.NONE:
  198. return { width: 'auto', height: 'auto' };
  199. case ObjectFit.CONTAIN:
  200. return (imageAspectRatio < containerAspectRatio) ? { width: 'auto' } : { height: 'auto' };
  201. case ObjectFit.COVER:
  202. return (imageAspectRatio < containerAspectRatio) ? { height: 'auto' } : { width: 'auto' };
  203. default:
  204. return {};
  205. }
  206. },
  207. clickHandler() {
  208. // don't show viewer when preview is false
  209. if (!this.preview) {
  210. return;
  211. }
  212. // prevent body scroll
  213. prevOverflow = document.body.style.overflow;
  214. document.body.style.overflow = 'hidden';
  215. this.showViewer = true;
  216. },
  217. closeViewer() {
  218. document.body.style.overflow = prevOverflow;
  219. this.showViewer = false;
  220. }
  221. }
  222. };
  223. </script>