Vue List Image Lazy Loading 구현해보자

slowin·2019년 8월 29일
8
post-thumbnail

1. Vue project 만들기

# 뷰가 설치되어 있지 않다면 설치합니다.
$ yarn global add @vue/cli

$ vue create vue-lazy-loading
$ cd vue-lazy-loading

# 서버 실행
$ yarn serve or npm run serve

2. 이미지 리스트 컴포넌트 생성

  • ImageList.vue 생성
  • ImageItem.vue 생성

기본 세팅 Code 보기

### App.vue
<template>
  <div id="app">
    <ImageList />
  </div>
</template>

<script>
import ImageList from './components/ImageList.vue'

export default {
  name: 'app',
  components: {
    ImageList,
  }
}
</script>

<style>

</style>

### ImageList.vue
<template>
  <div class="img-list">
      <ImageItem />
  </div>
</template>

<script>
import ImageItem from './ImageItem'

export default {
  name: 'ImageList',
  components: {
      ImageItem,
  },
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

### ImageItem.vue
<template>
  <div class="img-item">
      이미지 아이템 
  </div>
</template>

<script>
export default {
  name: 'ImageItem',
  props: {
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

3. 이미지 리스트 렌더링 해보기

  • 이미지를 보여줄려면 이미지가 필요하겠죠?
    https://picsum.photos/
    위 사이트 랜덤 이미지 URL을 활용 해보겠습니다.
### ImageItem.vue
<template>
  <div class="img-item">
      <img :src="imgSrc" /> <!-- 전달 받은 이미지 URL을 바인딩 -->
  </div>
</template>

<script>
export default {
  name: 'ImageItem',
  props: {
      imgSrc: String, // 부모 컴포넌트 이미지 URL props를 전달 받기위해 추가합니다
  }
}
</script>

### ImageList.vue
<template>
  <div class="img-list">
      <ImageItem img-src="https://picsum.photos/id/1/600/600" /> <!-- 이미지 URL을 전달합니다 -->
  </div>
</template>

<script>
import ImageItem from './ImageItem'

export default {
  name: 'ImageList',
  components: {
      ImageItem,
  },
}
</script>

4. 사용자 지정 디렉티브 만들기

  • ./src/directives/CustomDirective.js
  • ./src/directives/Index.js
  • vue directive에 대해서는 공식 사이트를 참고 해주세요
    https://kr.vuejs.org/v2/guide/custom-directive.html
  • 보통 저같은 경우는 Element에 접근할때 Directive를 만들어서 사용해요
### ./src/directives/CustomDirective.js

import Vue from 'vue';

const ImgLazyLoading = {
    inserted(el, binding) {
        console.log('directive inserted');
    },
    unbind(el, binding) {

    },
};

Vue.directive('img-lazy-loading', ImgLazyLoading);

### ./src/directives/Index.js

require('./CustomDirective');


### ./src/main.js

import Vue from 'vue'
import App from './App.vue'

require('./directives'); // directives 모듈 추가

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

### ./src/components/ImageItem.vue
- img 태그에 v-img-lazy-loading 디렉티브 속성을 추가
<template>
  <div class="img-item">
      <img :src="imgSrc" v-img-lazy-loading /> <!-- 전달 받은 이미지 URL을 바인딩 -->

  • 로그가 찍히는걸보니 잘 작동되고 하네요~

4. 100개 이미지 아이템 리스트 만들기

  • Lazy Loading을 구현하기 앞서 리스트를 만들어야겠죠?

  • v-for 디렉티브를 사용해서 구현합니다

    ./src/components/ImageList.vue

    5. Lazy Load Directive 코딩

    • IntersectionObserver API를 사용합니다.
      Element가 얼만큼 노출됬는지를 감시할수있습니다.
      참고(https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)

      ### ./src/directives/CustomDirective.js
      
      import Vue from 'vue';
      
      const ImgLazyLoading = {
          inserted(el, binding) {
              let options = {
                  root: document.querySelector(binding.value.rootScrollEl), // scroll event를 감시할 element를 설정 합니다
                  rootMargin: "500px 0px" 
              };
      
              // 이미지 로드 되기전에 placeholder를 보여주기 위해 element를 생성합니다
              var placeholderEl = document.createElement("div");  
              placeholderEl.setAttribute("style", "position: absolute; top: 0; left: 0; right: 0; bottom:0; background-color: #ccc;transition : opacity 1s ease-in-out;");
      
              el.appendChild(placeholderEl); 
      
              // 감시
              el.observer = new IntersectionObserver(changes => {
                  for (const change of changes) {
                      // element가 노출 되는것을 검사합니다
                      if(change.intersectionRatio > 0) {
                          // 노출이 되면 옵저버를 해제합니다
                          el.observer.disconnect();
      
                          var imgEl = document.createElement("img");  
                          imgEl.setAttribute("src", binding.value);
      
                          // 이미지 로드가 되면 img태그를 append합니다
                          // placeholder는 제거해줍니다.
                          imgEl.onload = function(){
                              console.log('img onload : ' + binding.value);
                              placeholderEl.style["opacity"] = 0;
                              el.appendChild(imgEl); 
                              setTimeout(() => {
                                  placeholderEl.remove();
                              },600)
                          };
                      }
                  }
              }, options);
      
              el.observer.observe(el);
          },
          unbind(el, binding) {
              if(el.observer) {
                  el.observer.disconnect();
              }
          },
      };
      
      Vue.directive('img-lazy-loading', ImgLazyLoading)

    마무리

    끝.

    결과물 : https://drive.google.com/open?id=1gUA1e0qLkHez7pBQPYFptwSfmSebyRpm

    github : https://github.com/KTH12/vue-lazy-loading-example

profile
slow and steady wins the race

0개의 댓글