TitleVisual.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <template>
  2. <section
  3. class="title--visual"
  4. :data-type="type"
  5. :data-theme="theme"
  6. ref="titleVisualRef"
  7. >
  8. <div class="title--visual--wrapper">
  9. <div
  10. class="title--visual--content"
  11. :data-align="textAlign"
  12. :data-animated="animation"
  13. :class="{ visible: isVisible }"
  14. >
  15. <h2 class="title--main">{{ title }}</h2>
  16. <span class="title--description">{{ description }}</span>
  17. <div v-if="$slots.default" class="title--additional">
  18. <slot></slot>
  19. </div>
  20. </div>
  21. </div>
  22. </section>
  23. </template>
  24. <script setup>
  25. // Props 정의
  26. const props = defineProps({
  27. title: {
  28. type: String,
  29. required: true,
  30. default: "특별함을 창조하다",
  31. },
  32. description: {
  33. type: String,
  34. required: true,
  35. default:
  36. "아우디 RS e-tron GT, 아우디 RS 6 Avant, 아우디 RS Q8의 예에서 여러 옵션을 결합하여 Audi exclusive order의 다양성과 개인 맞춤 구성 옵션을 경험해 보세요. 3D 버튼을 클릭하여 3D 보기를 활성화하면 인터랙티브 경험을 할 수 있습니다.",
  37. },
  38. textAlign: {
  39. type: String,
  40. default: "center", // 'left', 'center', 'right'
  41. validator: (value) => ["left", "center", "right"].includes(value),
  42. },
  43. theme: {
  44. type: String,
  45. default: "light", // 'light', 'dark'
  46. validator: (value) => ["light", "dark"].includes(value),
  47. },
  48. type: {
  49. type: String,
  50. default: "", // 'light', 'dark'
  51. validator: (value) => ["", "middle"].includes(value),
  52. },
  53. animation: {
  54. type: Boolean,
  55. default: true,
  56. },
  57. animationDelay: {
  58. type: Number,
  59. default: 300, // milliseconds
  60. },
  61. });
  62. // 애니메이션 로직
  63. import { onMounted, ref } from "vue";
  64. const isVisible = ref(false);
  65. const titleVisualRef = ref(null);
  66. onMounted(() => {
  67. if (props.animation) {
  68. // IntersectionObserver로 스크롤 애니메이션 구현
  69. const observer = new IntersectionObserver(
  70. (entries) => {
  71. entries.forEach((entry) => {
  72. if (entry.isIntersecting) {
  73. setTimeout(() => {
  74. isVisible.value = true;
  75. }, props.animationDelay);
  76. observer.unobserve(entry.target);
  77. }
  78. });
  79. },
  80. {
  81. threshold: 0.3, // 30% 보일 때 애니메이션 시작
  82. }
  83. );
  84. // ref를 통해 현재 컴포넌트 요소만 선택
  85. if (titleVisualRef.value) {
  86. observer.observe(titleVisualRef.value);
  87. }
  88. // cleanup
  89. return () => {
  90. if (titleVisualRef.value) {
  91. observer.unobserve(titleVisualRef.value);
  92. }
  93. };
  94. } else {
  95. isVisible.value = true;
  96. }
  97. });
  98. </script>