SwiperBanner.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <section class="swiper--banner--wrapper" :data-type="type" :data-fit="fit">
  3. <div class="swiper--banner--container">
  4. <div class="swiper-button-prev" ref="prevRef" v-if="slides.length > 1">
  5. <svg
  6. xmlns="http://www.w3.org/2000/svg"
  7. width="24"
  8. height="24"
  9. viewBox="0 0 24 24"
  10. fill="none"
  11. >
  12. <path
  13. d="M15 18L9 12L15 6"
  14. stroke="white"
  15. stroke-width="2"
  16. stroke-linecap="round"
  17. stroke-linejoin="round"
  18. />
  19. </svg>
  20. </div>
  21. <div class="swiper-pagination" ref="paginationRef" v-if="slides.length > 1"></div>
  22. <div class="swiper-button-next" ref="nextRef" v-if="slides.length > 1">
  23. <svg
  24. xmlns="http://www.w3.org/2000/svg"
  25. width="24"
  26. height="24"
  27. viewBox="0 0 24 24"
  28. fill="none"
  29. >
  30. <path
  31. d="M9 18L15 12L9 6"
  32. stroke="white"
  33. stroke-width="2"
  34. stroke-linecap="round"
  35. stroke-linejoin="round"
  36. />
  37. </svg>
  38. </div>
  39. <!-- 70% 영역: 단일 배너 -->
  40. <div class="swiper--banner--section">
  41. <div class="swiper--container" ref="swiperContainer">
  42. <div class="swiper-wrapper">
  43. <div v-for="(slide, index) in slides" :key="index" class="swiper-slide">
  44. <div class="slide--image" v-if="slide.image">
  45. <picture>
  46. <source
  47. v-if="slide.moImage"
  48. media="(max-width: 768px)"
  49. :srcset="slide.moImage"
  50. />
  51. <img
  52. :src="slide.image"
  53. :alt="slide.alt || 'Banner Image'"
  54. loading="lazy"
  55. />
  56. </picture>
  57. <div class="desc--wrapper">
  58. <h2
  59. v-if="getSlideTitle(index)"
  60. class="main--title"
  61. v-html="getSlideTitle(index)"
  62. ></h2>
  63. <h3 v-if="getSlideSubtitle(index)" class="sub--title">
  64. {{ getSlideSubtitle(index) }}
  65. </h3>
  66. <h4 v-if="getSlideSmalldesc(index)" class="desc--title">
  67. {{ getSlideSmalldesc(index) }}
  68. </h4>
  69. <h4
  70. v-if="getSlideCautiondesc(index)"
  71. class="caution--title"
  72. v-html="getSlideCautiondesc(index)"
  73. ></h4>
  74. <NuxtLink
  75. v-if="getSlideMorelink(index)"
  76. class="more--detail--href mt--45"
  77. :to="getSlideMorelink(index)"
  78. :target="getSlideTarget(index)"
  79. >{{ getSlideMorelinktitle(index) }} <i class="ico"></i
  80. ></NuxtLink>
  81. </div>
  82. </div>
  83. <div class="slide--image" v-if="slide.video">
  84. <video
  85. autoplay
  86. muted
  87. loop
  88. playsinline
  89. :poster="slide.video"
  90. controlslist="nodownload noplaybackrate"
  91. disablepictureinpicture
  92. preload="metadata"
  93. >
  94. <source media="(min-width:320px)" :src="slide.video" type="video/mp4" />
  95. </video>
  96. </div>
  97. </div>
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. </section>
  103. </template>
  104. <script setup>
  105. import { Swiper } from "swiper";
  106. import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
  107. import "swiper/css";
  108. import "swiper/css/navigation";
  109. import "swiper/css/pagination";
  110. import "swiper/css/effect-fade";
  111. const { getMediaUrl } = useImage();
  112. // Props 정의
  113. const props = defineProps({
  114. slides: {
  115. type: Array,
  116. default: () => [],
  117. },
  118. height: {
  119. type: String,
  120. default: "60%",
  121. },
  122. title: {
  123. type: String,
  124. default: "",
  125. },
  126. subtitle: {
  127. type: String,
  128. default: "",
  129. },
  130. smalldesc: {
  131. type: String,
  132. default: "",
  133. },
  134. cautiondesc: {
  135. type: String,
  136. default: "",
  137. },
  138. morelink: {
  139. type: String,
  140. default: "",
  141. },
  142. morelinktitle: {
  143. type: String,
  144. default: "자세히 보기",
  145. },
  146. morelinktarget: {
  147. type: String,
  148. default: "_self",
  149. },
  150. notice: {
  151. type: String,
  152. default: "",
  153. },
  154. autoplay: {
  155. type: [Boolean, Object],
  156. default: () => ({ delay: 5000 }),
  157. },
  158. loop: {
  159. type: Boolean,
  160. default: true,
  161. },
  162. type: {
  163. type: String,
  164. default: "horizental", // 'horz' , 'vert'
  165. validator: (value) => ["horizental", "vertical"].includes(value),
  166. },
  167. fit: {
  168. type: String,
  169. default: "cover",
  170. validator: (value) => ["cover", "contain"].includes(value),
  171. },
  172. });
  173. // Refs
  174. const swiperContainer = ref(null);
  175. const paginationRef = ref(null);
  176. const prevRef = ref(null);
  177. const nextRef = ref(null);
  178. const textSlider = ref(null);
  179. let swiperInstance = null;
  180. // 현재 슬라이드 인덱스
  181. const currentSlide = ref(0);
  182. // 슬라이드별 텍스트 반환 함수들
  183. const getSlideTitle = (index) => {
  184. return props.slides[index]?.title || props.title;
  185. };
  186. const getSlideSubtitle = (index) => {
  187. return props.slides[index]?.subtitle || props.subtitle;
  188. };
  189. const getSlideSmalldesc = (index) => {
  190. return props.slides[index]?.smalldesc || props.smalldesc;
  191. };
  192. const getSlideMorelink = (index) => {
  193. return props.slides[index]?.morelink || props.morelink;
  194. };
  195. const getSlideMorelinktitle = (index) => {
  196. return props.slides[index]?.morelinktitle || props.morelinktitle;
  197. };
  198. const getSlideTarget = (index) => {
  199. return props.slides[index]?.morelinktarget || props.morelinktarget;
  200. };
  201. const getSlideCautiondesc = (index) => {
  202. return props.slides[index]?.cautiondesc || props.cautiondesc;
  203. };
  204. onMounted(() => {
  205. // Swiper 인스턴스 초기화
  206. swiperInstance = new Swiper(swiperContainer.value, {
  207. modules: [Navigation, Pagination, Autoplay],
  208. slidesPerView: 1,
  209. spaceBetween: 0,
  210. loop: props.loop,
  211. autoplay: props.autoplay
  212. ? {
  213. delay:
  214. typeof props.autoplay === "object" ? props.autoplay.delay || 5000 : 5000,
  215. disableOnInteraction: false,
  216. pauseOnMouseEnter: true,
  217. }
  218. : false,
  219. navigation: {
  220. nextEl: nextRef.value,
  221. prevEl: prevRef.value,
  222. },
  223. pagination: {
  224. el: paginationRef.value,
  225. clickable: true,
  226. type: "bullets",
  227. // renderBullet: function (index, className) {
  228. // return '<span class="' + className + '">' + (index + 1) + "</span>";
  229. // },
  230. },
  231. effect: "fade",
  232. fadeEffect: {
  233. crossFade: true,
  234. },
  235. speed: 800,
  236. // 슬라이드 변경 이벤트
  237. on: {
  238. slideChange: function () {
  239. if (this && this.realIndex !== undefined) {
  240. currentSlide.value = this.realIndex;
  241. }
  242. // 모든 비디오 일시정지
  243. const allVideos = this.el.querySelectorAll("video");
  244. allVideos.forEach((video) => {
  245. video.pause();
  246. video.currentTime = 0;
  247. });
  248. // 현재 슬라이드의 비디오 재생
  249. const currentSlideEl = this.slides[this.activeIndex];
  250. if (currentSlideEl) {
  251. const video = currentSlideEl.querySelector("video");
  252. if (video) {
  253. video.play().catch((err) => console.log("Video play failed:", err));
  254. }
  255. }
  256. },
  257. init: function () {
  258. if (this && this.realIndex !== undefined) {
  259. currentSlide.value = this.realIndex;
  260. }
  261. // 초기 슬라이드의 비디오 재생
  262. const currentSlideEl = this.slides[this.activeIndex];
  263. if (currentSlideEl) {
  264. const video = currentSlideEl.querySelector("video");
  265. if (video) {
  266. video.play().catch((err) => console.log("Video play failed:", err));
  267. }
  268. }
  269. },
  270. },
  271. });
  272. });
  273. onUnmounted(() => {
  274. if (swiperInstance) {
  275. swiperInstance.destroy(true, true);
  276. }
  277. });
  278. </script>