본문 바로가기
쬬는 개발중

masonry / infinite scroll / imagesLoaded 활용하여 핀터레스트 | 네이버 이미지 리스트화면 구현하기

by Joooooooo 2022. 12. 9.
반응형

벽돌처럼 차곡차곡쌓아진

디자인을 masonry 라고 이야기한다. 이번엔 사이즈가 제각각 다른 이미지를 활용해 이런 피드리스트를 구현해보았다. 추가로 스크롤링시 다음 페이지를 호출하는 방식을 사용했다. (infinite scroll)

 

 

핀터레스트 네이버

 

masonry 만 사용한 경우

발생되는 문제점이 애초에 해당 프로젝트는 pull-refresh 를 사용하고 있어서 제대로 영역을 감지 못하는 상태였다. 게다가 데이터 내부에 이미지를 받아오자마자 뿌려주다보니 이미지가 완전히 load 되기 전에 레이아웃이 실행되어 높이 영역이 계속해서 겹치는 현상이 발생했다.  그럼 어떻게 해결할 수 있을까? 라는 생각을 이것저것 해보다가 이미지가 완전히 load 되어 높이가 지정되었을 때, 적용하면 문제없이 구현이 가능할것이라고 생각했다.

 

 

사용 라이브러리 

👉 masonry

 

Masonry

Install Download CDN Link directly to Masonry files on unpkg. Package managers Install with Bower:  bower install masonry --save Install with npm:  npm install masonry-layout Getting started HTML Include the Masonry .js file in your site. Masonry works o

masonry.desandro.com

 

 

👉 vue-image-loaded 

리액트나 다른 구성이면 맞춰서 사용하면 된다

 

vue-images-loaded

Vue.js 2.0 directive to detect image loading. Latest version: 1.1.2, last published: 6 years ago. Start using vue-images-loaded in your project by running `npm i vue-images-loaded`. There are 7 other projects in the npm registry using vue-images-loaded.

www.npmjs.com

 

 

👉 패키지 설치 

npm install masonry-layout
npm install vue-images-loaded

 

 

👉  index.vue

  import Masonry from "masonry-layout";
  import ImagesLoaded from "imagesloaded";

 

 

👉  html or template

 <div id="grid">
  <div class="box" v-for="(item, index) in boxItem" :key="index">
  	<img :src="item.src" />
  </div>
</div>

 

 

👉  script

data(){
	return {
    	boxItem: [],
        page: 1,
    }
},



mounted(){
	 this.getData(); // 초기 데이터 호출 
	 window.addEventListener("scroll", this.handler); // 스크롤 시 호출
},



methods:{
	getData(){ 
        axios.get('url',params).then((data)=>{
         // boxItem에 담을 요소를 넣어준다
         this.boxItem = [...this.boxItem, ...data.arr ]; 
         // boxItem 기준으로 grid 생성
        this.$nextTick(() => { this.createGrid();}
        
        this.timeLoading = false;
        this.bottomLoading = false;
      })
    },
    
    
	createGrid() {
        if (!this.boxItem.length > 0) return;
        this.$nextTick(() => {
          this.imagesLoaded();
        });
    },
    
    
    
    imagesLoaded() {
        const grid = document.querySelector("#grid");
        return new ImagesLoaded(grid, () => {
            this.$nextTick(() => {
                 // 이미지 로드 후 masonry 호출 
             	 this.masonry(); 
              });
        });
    },
    
    
    masonry() {
        const grid = document.querySelector("#grid");
        new Masonry(grid, {
          percentPosition: true, 
          itemSelector: ".box",
        });
      },
      


   async handler() { 
    if (this.timeLoading === false) {
      this.scroll = window.scrollY;
      const SCROLLED_HEIGHT = window.scrollY;
      const WINDOW_HEIGHT = window.innerHeight;
      const DOC_TOTAL_HEIGHT = document.body.offsetHeight;
      const IS_BOTTOM =
        WINDOW_HEIGHT + SCROLLED_HEIGHT + 4000 >= DOC_TOTAL_HEIGHT;
 
 	 // 분기 조건 :: 호출중, 마지막 페이지가 아닌경우
      if (
        IS_BOTTOM &&
        this.lastPage === false && // 마지막 페이지 체크
        this.timeLoading === false // axios 호출 상태 체크 
      ) {
        this.timeLoading = true;
        setTimeout(() => {
          if (!this.lastPage) {
            let page = this.page + 1;
            this.page = page;
            this.getData(); // axios 호출
          }
        }, 700);
      }
    }
  },
 }

 

 

반응형