vue3 に aos を適用しアニメーションさせる

vue3 に aos を適用しアニメーションさせる

2024/04/29 12:00:00
Program
Git-Bash, Node, Vite, Vue, Bootstrap, Sass, SPA, Aos

前提 #

aos をインストールする #

$ npm install aos

aos を vue3 で使えるようにする #

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './scss/styles.scss'
import 'animate.css/animate.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
import * as bootstrap from 'bootstrap'
import AOS from 'aos' // 追加
import 'aos/dist/aos.css' // 追加

const app = createApp(App)
app.use(router)
app.use(AOS.init()) // 追加
app.mount('#app')

カードを絞り込む #

  • 絞り込むカテゴリ一覧を設定
    <template>
    ...
    <div class="container" data-aos="fade-up" data-aos-delay="200">
        <button v-for="(category,index) in categories" 
            :key="index"
            :filter="category.name"
            :class="{ active: activeIndex === index }"
            class="btn btn-outline-secondary btn-sm m-2" 
            @click="changeCategory($event,index)">{{category.title}}
        </button>
    </div>
    ...
    </template>
    
    <script setup>
    ...
    const categories = ref([
    {
        'name': 'All',
        'title': 'すべて',
    },
    {
        'name': '血液検査',
        'title': '血液検査',
    },
    {
        'name': '生化学検査',
        'title': '生化学検査',
    },
    {
        'name': 'C',
        'title': 'C',
    },
    {
        'name': 'D',
        'title': 'D',
    },
    ]);
    ...
    </script>
    
  • 絞り込み対象のカード一式を設定
    <template>
    ...
    <div class="container" data-aos="fade-up" data-aos-delay="500">
        <div class="grid">
            <div v-for="item in items" class="item" :data-category="item.category">
                <div class="item-content">
                    <img :src="item.image" style="width:100%;height:100%;">
                    <div class="card-body">
                        <p class="card-title">{{item.name}}</p>
                        <div class="card-text">{{item.docs}}</div>
                        <a :href="item.link" class="btn btn-primary btn-sm" role="button">詳細</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
    ...
    </template>
    
    <script setup>
    ...
    const items = ref([
    { 
        category: '血液検査',
        name: 'EYM-230 動物用',
        docs: '',
        image: image_01,
        link: '/productions/01',
    },
    { 
        category: '血液検査',
        name: '全自動血球計数装置 PCE-350',
        docs: '',
        image: image_02,
        link: '/productions/01',
    },
    { 
        category: '血液検査',
        name: '全自動血球計数装置 EYM-230',
        docs: '',
        image: image_03,
        link: '/productions/01',
    },
    { 
        category: '生化学検査',
        name: 'マイクロフローセル生化学分析装置 AE-600F(フィルター式)',
        docs: '',
        image: image_04,
        link: '/productions/01',
    },
    { 
        category: '生化学検査',
        name: 'マイクロフローセル生化学分析装置 AE-600P(分光式)',
        docs: '',
        image: image_04,
        link: '/productions/01',
    },
    ]);
    
    ...
    </script>
    
  • その他js設定
    const muuriGrid = ref(null); // Muuri インスタンスを格納するための ref
    const activeIndex = ref(0);
    onMounted( async () => {
        muuriGrid.value = new Muuri('.grid', {
        });
    });
    
    const changeCategory = (event, index) => {
        var category = event.target.getAttribute('filter');
        activeIndex.value = index;
        if(category == 'All'){
            muuriGrid.value.filter('*');
        }
        else{
            muuriGrid.value.filter(`.item[data-category="${category}"]`);
        }
    };
    

以下、サンプルコードまとめ

$ emacs src/components/home.vue

<template>
  <Header />

  <div class="container" data-aos="fade-up" data-aos-delay="200">
    <button v-for="(category,index) in categories" 
            :key="index"
            :filter="category.name"
            :class="{ active: activeIndex === index }"
            class="btn btn-outline-secondary btn-sm m-2" 
            @click="changeCategory($event,index)">{{category.title}}
    </button>
  </div>

  <div class="container" data-aos="fade-up" data-aos-delay="500">
    <div class="grid">
      <div v-for="item in items" class="item" :data-category="item.category">
        <div class="item-content">
          <img :src="item.image" style="width:100%;height:100%;">
          <div class="card-body">
            <p class="card-title">{{item.name}}</p>
            <div class="card-text">{{item.docs}}</div>
            <a :href="item.link" class="btn btn-primary btn-sm" role="button">詳細</a>
          </div>
        </div>
      </div>
    </div>
  </div>

  <Footer />
</template>

<script setup>
import { onMounted, ref } from 'vue';
import 'web-animations-js';
import Muuri from 'muuri';
import Header from './Header.vue'
import Footer from './Footer.vue'
import image_01 from '@/assets/images/portfolio/portfolio-1.jpg'
import image_02 from '@/assets/images/portfolio/portfolio-2.jpg'
import image_03 from '@/assets/images/portfolio/portfolio-3.jpg'
import image_04 from '@/assets/images/portfolio/portfolio-4.jpg'
import image_05 from '@/assets/images/portfolio/portfolio-5.jpg'

// const pageName = ref('test');

// const grid = ref(null);
const categories = ref([
  {
    'name': 'All',
    'title': 'すべて',
  },
  {
    'name': '血液検査',
    'title': '血液検査',
  },
  {
    'name': '生化学検査',
    'title': '生化学検査',
  },
  {
    'name': 'C',
    'title': 'C',
  },
  {
    'name': 'D',
    'title': 'D',
  },
]);

const items = ref([
  { 
    category: '血液検査',
    name: 'EYM-230 動物用',
    docs: '',
    image: image_01,
    link: '/productions/01',
  },
  { 
    category: '血液検査',
    name: '全自動血球計数装置 PCE-350',
    docs: '',
    image: image_02,
    link: '/productions/01',
  },
  { 
    category: '血液検査',
    name: '全自動血球計数装置 EYM-230',
    docs: '',
    image: image_03,
    link: '/productions/01',
  },
  { 
    category: '生化学検査',
    name: 'マイクロフローセル生化学分析装置 AE-600F(フィルター式)',
    docs: '',
    image: image_04,
    link: '/productions/01',
  },
  { 
    category: '生化学検査',
    name: 'マイクロフローセル生化学分析装置 AE-600P(分光式)',
    docs: '',
    image: image_04,
    link: '/productions/01',
  },
]);
// const filter = ref('A'); // フィルタリングするカテゴリ
const muuriGrid = ref(null); // Muuri インスタンスを格納するための ref
const activeIndex = ref(0);

onMounted( async () => {
  muuriGrid.value = new Muuri('.grid', {
    // dragEnabled: true
  });
  // window.addEventListener('load', function () {
  //   muuriGrid.value.refreshItems().layout();
  // });
  // await nextTick();
  // AOS.init({
  //   startEvent: 'load',
  //   once: true,
  // });
});

const changeCategory = (event, index) => {
  var category = event.target.getAttribute('filter');
  activeIndex.value = index;
  if(category == 'All'){
    muuriGrid.value.filter('*');
  }
  else{
    muuriGrid.value.filter(`.item[data-category="${category}"]`);
  }
};

</script>

<style lang="scss" scoped>
.grid {
  /* position: relative; */
}

.item {
  display: block;
  position: absolute;
  /* z-index: 1; */
  width: 270px;
  height: 270px;
  background-size: cover;
  margin: 20px 60px;
}

.card-body {
  position: absolute;
  bottom: 0;
  /* padding: 5px; */
  top: auto;
  /* transform: translateY(50%); */
  background-color: rgba(255, 255, 255, 0.85);
  /* color: #fff; */
  top: 65%;
  width: 100%;
  height: 35%;
  text-align: center;
  border: 1px solid rgba(80, 80, 80, 0.2);; /* ボーダーを指定 */
  border-radius: 5px; /* 角を丸める */
}

/*
.item.muuri-item-dragging {
  z-index: 3;
}

.item.muuri-item-releasing {
  z-index: 2;
}

.item.muuri-item-hidden {
  z-index: 0;
}
*/
</style>