tui-lazyload-img.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <view class="tui-lazyload__box"
  3. :style="{backgroundColor:placeholder?'transparent':backgroundColor,width:width,height:height?height:'auto',borderRadius:radius}"
  4. @tap="handleClick">
  5. <image class="tui-lazyload__img"
  6. :class="{'tui-img__hidden':!placeholder && fadeShow && !show,'tui-img__appear':show && !placeholder && fadeShow}"
  7. :style="{height:height,borderRadius:radius}" :src="show?src:placeholder"
  8. :mode="height&&height!==true?mode:'widthFix'" :webp="webp" :show-menu-by-longpress="showMenuByLongpress"
  9. :draggable="draggable" @load="load" @error="error" :id="elId">
  10. </image>
  11. <slot></slot>
  12. </view>
  13. </template>
  14. <script>
  15. export default {
  16. name: "tui-lazyload-img",
  17. emits: ['error', 'load', 'click'],
  18. options: {
  19. virtualHost: true
  20. },
  21. props: {
  22. //图片路径
  23. src: {
  24. type: String,
  25. default: ''
  26. },
  27. //占位图路径
  28. placeholder: {
  29. type: String,
  30. default: ''
  31. },
  32. //占位背景色,placeholder有值时失效
  33. backgroundColor: {
  34. type: String,
  35. default: '#E7E7E7'
  36. },
  37. //图片的裁剪模式,参考image组件mode属性
  38. mode: {
  39. type: String,
  40. default: 'widthFix'
  41. },
  42. //图片显示动画效果,无占位图时有效
  43. fadeShow: {
  44. type: Boolean,
  45. default: true
  46. },
  47. //默认不解析 webP 格式,只支持网络资源 微信小程序2.9.0
  48. webp: {
  49. type: Boolean,
  50. default: false
  51. },
  52. //开启长按图片显示识别小程序码菜单 微信小程序2.7.0
  53. showMenuByLongpress: {
  54. type: Boolean,
  55. default: false
  56. },
  57. //鼠标长按是否能拖动图片 仅H5平台 3.1.1+ 有效
  58. draggable: {
  59. type: Boolean,
  60. default: true
  61. },
  62. //图片宽度
  63. width: {
  64. type: String,
  65. default: '340rpx'
  66. },
  67. //图片高度,如果高度设置为auto,mode值需要设置为widthFix
  68. height: {
  69. type: String,
  70. default: '340rpx'
  71. },
  72. //图片圆角值,如:10rpx
  73. radius: {
  74. type: String,
  75. default: '0'
  76. },
  77. //节点布局区域的下边界,目标节点区域以下 bottom(px) 时,就会触发回调函数
  78. bottom: {
  79. type: [Number, String],
  80. default: 50
  81. },
  82. //是否停止监听,设置为true时回调函数将不再触发
  83. disconnect: {
  84. type: Boolean,
  85. default: false
  86. },
  87. //图片在列表中的索引值
  88. index: {
  89. type: Number,
  90. default: 0
  91. }
  92. },
  93. data() {
  94. let elId = this.unique() + this.index
  95. return {
  96. show: false,
  97. elId: elId
  98. };
  99. },
  100. watch: {
  101. disconnect(val) {
  102. if (val) {
  103. this.removeObserver()
  104. }
  105. }
  106. },
  107. created() {
  108. this.observer = null;
  109. // this.elId = this.unique() + this.index;
  110. },
  111. mounted() {
  112. this.$nextTick(() => {
  113. setTimeout(() => {
  114. // #ifndef H5
  115. if (!this.disconnect) {
  116. this.initObserver()
  117. } else {
  118. this.show = true;
  119. }
  120. // #endif
  121. // #ifdef H5
  122. if (!this.disconnect && window.self === window.top) {
  123. this.initObserver()
  124. } else {
  125. this.show = true;
  126. }
  127. // #endif
  128. }, 50)
  129. })
  130. },
  131. // #ifndef VUE3
  132. beforeDestroy() {
  133. this.removeObserver()
  134. },
  135. // #endif
  136. // #ifdef VUE3
  137. beforeUnmount() {
  138. this.removeObserver()
  139. },
  140. // #endif
  141. methods: {
  142. unique: function(n) {
  143. n = n || 6;
  144. let rnd = '';
  145. for (let i = 0; i < n; i++)
  146. rnd += Math.floor(Math.random() * 10);
  147. return 'tui_' + new Date().getTime() + rnd;
  148. },
  149. removeObserver() {
  150. if (this.observer) {
  151. this.observer.disconnect()
  152. this.observer = null;
  153. }
  154. },
  155. initObserver() {
  156. if (this.observer || this.show) return;
  157. try {
  158. let element = this.elId ? `#${this.elId}` : '.tui-lazyload__img';
  159. const observer = uni.createIntersectionObserver(this)
  160. observer.relativeToViewport({
  161. bottom: Number(this.bottom) || 50
  162. }).observe(element, (res) => {
  163. if (res.intersectionRatio > 0 && !this.show) {
  164. this.show = true;
  165. this.removeObserver()
  166. }
  167. })
  168. this.observer = observer
  169. } catch (e) {
  170. //TODO handle the exception
  171. this.show = true;
  172. this.removeObserver()
  173. }
  174. },
  175. error(e) {
  176. if (!this.show) return;
  177. this.$emit('error', {
  178. detail: e.detail,
  179. index: this.index
  180. })
  181. },
  182. load(e) {
  183. if (!this.show) return;
  184. this.$emit('load', {
  185. detail: e.detail,
  186. index: this.index
  187. })
  188. },
  189. handleClick(e) {
  190. this.$emit('click', {
  191. index: this.index
  192. })
  193. }
  194. }
  195. }
  196. </script>
  197. <style scoped>
  198. .tui-lazyload__box {
  199. display: inline-flex;
  200. position: relative;
  201. flex-shrink: 0;
  202. }
  203. .tui-lazyload__img {
  204. width: 100%;
  205. display: block;
  206. flex-shrink: 0;
  207. transition: opacity .3s linear;
  208. }
  209. .tui-img__hidden {
  210. opacity: 0;
  211. visibility: hidden;
  212. }
  213. .tui-img__appear {
  214. opacity: 1;
  215. visibility: visible;
  216. }
  217. </style>