vue3 に aos を適用しアニメーションさせる
2024/04/29 12:00:00
前提 #
- 動作環境は git-bash とする
 構築手順は以下を参考にする
 https://blog.oya3.net/posts/2023/10/03/00_git/
- node21 + vite5 + vue3 + bootstrap5 + sass を使用する
 構築手順は以下を参考にする
 http://blog.oya3.net/posts/2024/04/27/vite-vue-bootstrap-sass/
- 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>