DEV Community

Cover image for Vue responsive infinite-scroll component with Vuex and SCSS
Larissa Avila
Larissa Avila

Posted on • Updated on

Vue responsive infinite-scroll component with Vuex and SCSS

Technologies to use:

Step 1
Generate a project with vue cli:

vue create infinite-scroll
Enter fullscreen mode Exit fullscreen mode

Select this option

? Please pick a preset: (Use arrow keys)
  tipical (node-sass, babel, router, vuex)
  default (babel, eslint)
> Manually select features
Enter fullscreen mode Exit fullscreen mode

Select these with tab space, then enter

? Check the features needed for your project:
 > ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 > ◉ Vuex
 > ◉ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
Enter fullscreen mode Exit fullscreen mode
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
  Sass/SCSS (with dart-sass)
> Sass/SCSS (with node-sass)
  Less
  Stylus
Enter fullscreen mode Exit fullscreen mode

Step 2

In our components folder we will add this 2 components:

Button.vue

<template>
  <button
    v-if="ui !== 'submit'"
    @click="btnClick"
    :type="type"
    :class="ui ? 'btn__' + ui : 'btn__primary'"
  >
    {{ text }}
  </button>
  <button
    v-else
    @click="btnClick"
    formData.action="/submit"
    :type="type"
    :class="ui ? 'btn__' + ui : 'btn__primary'"
  >
    {{ text }}
  </button>
</template>
<script>
export default {
  name: "btn-fc",
  props: ["text", "type", "disabled", "ui"],
  methods: {
    btnClick() {
      this.$emit("btnClick");
    },
  },
};
</script>

Enter fullscreen mode Exit fullscreen mode

CardItem.vue

<template>
  <div class="carditem">
    <div class="carditem__image">
      <img :src="pathBase+'futurama'+character.id+'.jpg'" />
    </div>
    <div class="carditem__info">
      <strong class="carditem__title">{{ character.name.first }} {{ character.name.last }}</strong>
      <span>{{ character.occupation }}</span>
      <span>{{ character.species }}</span>
    </div>
  </div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
  name: "CardItem",

  props: {
    character: {
      type: Object,
    },
    index: Number,
    showPage: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      pathBase: "https://res.cloudinary.com/lariicsa/image/upload/v1602727260/futurama/",
      charImages: [
        { name: "Philip", url: "" },
        { name: "Turanga", url: "" },
        { name: "Bender", url: "futurama4_pxj04y.jpg" },
        { name: "Hubert", url: "" },
        { name: "Amy", url: "" },
        { name: "Hermes", url: "" },
        { name: "Carol", url: "" },
        { name: "John", url: "" },
        { name: "Zapp", url: "" },
        { name: "Scruffy", url: "" },
        { name: "Cubert", url: "" },
        { name: "Kif", url: "" },
        { name: "Dwight", url: "" },
        { name: "LaBarbara", url: "" },
      ],
    };
  },

  mounted() {
    this.index;
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Step 3
In a new folder called assets we will add new folder called scss and then add these scss files

And we will modify the main.js file

import Vue from 'vue'
import axios from "axios";
import VueAxios from "vue-axios";
import App from './App.vue'
import Vuex from 'vuex'
import router from './router'
import store from './store'
import "./assets/scss/index.scss";

Vue.use(axios);
Vue.use(Vuex)
Vue.config.productionTip = false

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

Enter fullscreen mode Exit fullscreen mode

Step 4
We will create a new folder called service and in a index.js file we will call our service from a Futurama API from sampleapis.com

import axios from "axios";
const headers = {
  "Content-Type": "application/json",
};

const baseURL = () => {
  (axios.defaults.baseURL = "https://sampleapis.com/futurama/api"), headers;
};

const FUTURAMA_SERVICE = axios.create(baseURL());
const GET_DIMENSION = "/characters";

const characterInfo = (idCharacter) => FUTURAMA_SERVICE.get(GET_DIMENSION+idCharacter);
const characterAll = () => FUTURAMA_SERVICE.get(GET_DIMENSION);

export { characterInfo, characterAll };
Enter fullscreen mode Exit fullscreen mode

And in our Store folder at file index.js we will add next:

import Vue from 'vue'
import Vuex from 'vuex'
import { characterInfo, characterAll } from "../service/index"

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    characterInfo: {},
    charactersList: []
  },
  mutations: {
    setCharactersList:(state, payload) => (state.charactersList = payload),
    setCharacterInfo:(state, payload) => (state.characterInfo = payload)
  },
  actions: {
    async getCharactersList({commit}) {
      try {
        const response = await characterAll()
        const list = response.data
        commit("setCharactersList", list)
        console.log('all',list);
      } catch (error) {
        console.log(error.response);
      }
    },

    async getCharactersDimension({ commit }, idCharacter) {
      try {
        const response = await characterInfo(idCharacter)
        console.log('response', response);
      } catch (error) {
        console.log(error.response);
      }
    }
  },

  getters: {
    gtrCharacters(state) {
      return state.charactersList
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Step 5
Now we will create our important component InfiniteScroll.vue

<template>
  <div class="infscroll__container">
    <div id="infinite-list" class="infscroll__items">
      <transition name="fade">
        <div class="infscroll__loader-bg" v-show="showLoading">
          <div class="infscroll__loader">{{ textLoading }}</div>
        </div>
      </transition>
      <slot></slot>
      <ButtonFan
        v-show="isMobile"
        @btnClick="loadMore()"
        text="Load more ..."
        ui="primary infscroll"
      />
    </div>
  </div>
</template>
<script>
import ButtonFan from "@/components/Button";
export default {
  name: "InfiniteScroll",

  components: {
    ButtonFan,
  },

  props: {
    showLoading: {
      type: Boolean,
      default: false,
    },
    textLoading: {
      type: String,
      default: "Loading ...",
    },
  },

  data() {
    return {
      isMobile: false,
    };
  },

  mounted() {
    const listElm = document.querySelector("#infinite-list");
    listElm.addEventListener("scroll", (e) => {
      if (
        /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
          navigator.userAgent
        )
      ) {
        if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight) {
          this.isMobile = true;
        }
      } else {
        if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight) {
          this.loadMore();
        }
      }
    });
  },

  methods: {
    loadMore() {
      this.$emit("loadMore");
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Step 6
And finally we will set the Home View in Home.vue

<template>
  <div class="container">
    <div class="row center">
      <h1>Futurama <span>{{cards.length}} from {{gtrCharacters.length}}</span></h1>
    </div>
    <div class="row center">
      <InfiniteScroll :showLoading="loading" @loadMore="loadMore()">
        <div class="row between">
          <CardItem
            v-for="(character, index) in cards"
            :character="character"
            :key="index"
            :index="parseInt(index + 1)"
          />
        </div>
      </InfiniteScroll>
    </div>
  </div>
</template>
<script>
import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
import CardItem from "@/components/CardItem";
import InfiniteScroll from "@/components/InfiniteScroll";

export default {
  name: "Home",
  components: {
    CardItem,
    InfiniteScroll,
  },

  data() {
    return {
      loading: false,
      items: [],
      upto: 6,
      pages: [],
      pageArea: "",
      isMobile: false,
    };
  },

  static: {
    limitScrollItems: 6,
  },

  async created() {
    await this.getCharactersList();
  },

  mounted() {
    this.cards;
  },

  methods: {
    ...mapActions(["getCharactersList"]),

    loadMore() {
      const listItems = document.querySelector("#infinite-list");
      let heightToTop = listItems.scrollTop;
      if (
        /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
          navigator.userAgent
        )
      ) {
        setTimeout((e) => {
          listItems.scrollTo(0, heightToTop);
        }, 1000);
      }

      this.loading = true;
      setTimeout((e) => {
        const card = this.cards.map((item) => {
          this.cards.push(item);
        });
        this.upto += this.$options.static.limitScrollItems;
        this.loading = false;
      }, 800);
    },
  },

  computed: {
    ...mapState(["charactersList"]),
    ...mapGetters(["gtrCharacters"]),

    cards() {
      const card = this.gtrCharacters.slice(0, this.upto).map((item) => {
        return item;
      });
      return card;
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

And that's all folks!

You can check the code here https://github.com/Lariicsa/infinite-scroll/blob/master/src/components/InfiniteScroll.vue
And the demo here Infinite Scroll Futurama

Top comments (0)