관리 메뉴

개발쬬

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

쬬는 개발중

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

Joooooooo 2022. 12. 9. 16:27
반응형

벽돌처럼 차곡차곡쌓아진

디자인을 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);
      }
    }
  },
 }

 

 

반응형