swiper and clean

This commit is contained in:
2026-07-12 19:27:47 +00:00
parent 5cd55c096d
commit db9370f916
15 changed files with 250 additions and 2025 deletions

View File

@@ -0,0 +1,18 @@
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
export default function useAnimations(containerRef) {
const { pathname } = useLocation()
useEffect(() => {
const el = containerRef.current
if (!el) return
el.querySelectorAll('[data-ani]').forEach(el => {
el.classList.add(el.dataset.ani)
})
el.querySelectorAll('[data-ani-delay]').forEach(el => {
el.style.animationDelay = el.dataset.aniDelay
})
}, [containerRef, pathname])
}

View File

@@ -0,0 +1,36 @@
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
export default function useBackgroundAttrs(containerRef) {
const { pathname } = useLocation()
useEffect(() => {
const el = containerRef.current
if (!el) return
// data-bg-src → background-image
el.querySelectorAll('[data-bg-src]').forEach(el => {
el.style.backgroundImage = 'url(' + el.dataset.bgSrc + ')'
el.removeAttribute('data-bg-src')
el.classList.add('background-image')
})
// data-bg-color → background-color
el.querySelectorAll('[data-bg-color]').forEach(el => {
el.style.backgroundColor = el.dataset.bgColor
el.removeAttribute('data-bg-color')
})
// data-border → CSS var
el.querySelectorAll('[data-border]').forEach(el => {
el.style.setProperty('--th-border-color', el.dataset.border)
})
// data-mask-src → mask-image
el.querySelectorAll('[data-mask-src]').forEach(el => {
el.style.maskImage = 'url(' + el.dataset.maskSrc + ')'
el.style.webkitMaskImage = 'url(' + el.dataset.maskSrc + ')'
el.classList.add('bg-mask')
el.removeAttribute('data-mask-src')
})
}, [containerRef, pathname])
}

133
src/hooks/useSwiper.js Normal file
View File

@@ -0,0 +1,133 @@
import { useEffect, useRef } from 'react'
import { useLocation } from 'react-router-dom'
import Swiper from 'swiper'
import { Navigation, Pagination, Autoplay, EffectFade } from 'swiper/modules'
export default function useSwiper(containerRef) {
const { pathname } = useLocation()
const instances = useRef([])
useEffect(() => {
const container = containerRef.current
if (!container) return
// Destroy previous instances
instances.current.forEach(s => s.destroy(true, true))
instances.current = []
// Init all .th-slider elements
container.querySelectorAll('.th-slider').forEach(el => {
try {
const userOptions = JSON.parse(el.dataset.sliderOptions || '{}')
// Find pagination element — inside slider or sibling .slider-controller
let paginationEl = el.querySelector('.slider-pagination')
if (!paginationEl) {
const controller = el.closest('.th-slider')?.parentElement?.querySelector('.slider-controller')
if (controller) paginationEl = controller.querySelector('.slider-pagination')
}
const hasNumberPagi = el.querySelector('.slider-pagination.pagi-number')
const paginationType = userOptions.paginationType || 'bullets'
const swiper = new Swiper(el, {
modules: [Navigation, Pagination, Autoplay, EffectFade],
slidesPerView: userOptions.slidesPerView || 1,
spaceBetween: userOptions.spaceBetween !== undefined ? userOptions.spaceBetween : 24,
loop: userOptions.loop !== false,
speed: userOptions.speed || 1000,
autoplay: userOptions.autoplay || { delay: 6000, disableOnInteraction: false },
effect: userOptions.effect || 'slide',
centeredSlides: userOptions.centeredSlides || false,
slidesPerGroup: userOptions.slidesPerGroup || 1,
breakpoints: userOptions.breakpoints || {},
navigation: {
nextEl: el.querySelector('.slider-next'),
prevEl: el.querySelector('.slider-prev'),
},
pagination: paginationEl ? {
el: paginationEl,
type: paginationType,
clickable: true,
renderBullet: hasNumberPagi ? function (index, className) {
const num = index + 1
return '<span class="' + className + ' number">' + (num < 10 ? '0' + num : num) + '</span>'
} : undefined,
formatFractionCurrent: function (n) { return n < 10 ? '0' + n : n },
formatFractionTotal: function (n) { return n < 10 ? '0' + n : n },
} : false,
on: {
slideChange: function () {
setTimeout(function () {
if (swiper.params.mousewheel) swiper.params.mousewheel.releaseOnEdges = false
}, 500)
},
reachEnd: function () {
setTimeout(function () {
if (swiper.params.mousewheel) swiper.params.mousewheel.releaseOnEdges = true
}, 750)
},
...(userOptions.on || {}),
},
})
instances.current.push(swiper)
} catch (e) {
console.warn('useSwiper: failed to init', el.id || el.className, e)
}
})
// External controls: [data-slider-prev] and [data-slider-next]
const handleClick = (e) => {
const btn = e.target.closest('[data-slider-prev], [data-slider-next]')
if (!btn) return
const selector = btn.dataset.sliderPrev || btn.dataset.sliderNext
const sliderEl = document.querySelector(selector)
if (!sliderEl || !sliderEl.swiper) return
if (btn.dataset.sliderPrev) sliderEl.swiper.slidePrev()
else sliderEl.swiper.slideNext()
}
container.addEventListener('click', handleClick)
// Slider tabs: [data-slider-tab]
const tabContainers = container.querySelectorAll('[data-slider-tab]')
tabContainers.forEach(tc => {
const tabButtons = tc.querySelectorAll('.tab-btn')
const sliderSelector = tc.dataset.sliderTab
const sliderEl = document.querySelector(sliderSelector)
if (!sliderEl || !sliderEl.swiper || !tabButtons.length) return
const swiper = sliderEl.swiper
const setActive = (btn) => {
tabButtons.forEach(b => b.classList.remove('active'))
btn.classList.add('active')
}
tabButtons.forEach((btn, i) => {
btn.addEventListener('click', (e) => {
e.preventDefault()
setActive(btn)
swiper.slideTo(i)
})
})
swiper.on('slideChange', () => {
const idx = swiper.realIndex
if (tabButtons[idx]) setActive(tabButtons[idx])
})
// Set initial active
if (tabButtons[swiper.activeIndex]) {
tabButtons[swiper.activeIndex].classList.add('active')
}
})
return () => {
instances.current.forEach(s => s.destroy(true, true))
instances.current = []
container.removeEventListener('click', handleClick)
}
}, [containerRef, pathname])
}