[category_carousel ids="19,21,23,25,27,29"]
产品分类左右滑动
add_shortcode('category_carousel', function($atts) {
$cat_ids = !empty($atts['ids']) ? explode(',', $atts['ids']) : [];
if (empty($cat_ids)) return '';
$per_row = 3;
$cats = get_terms([
'taxonomy' => 'product_cat',
'include' => $cat_ids,
'hide_empty' => false,
'orderby' => 'include',
]);
if (empty($cats) || is_wp_error($cats)) return '';
$groups = array_chunk($cats, $per_row);
$total_groups = count($groups);
ob_start();
?>
<div class="cat-carousel-wrap">
<div class="cat-carousel-inner">
<?php foreach ($groups as $g=>$group): ?>
<div class="cat-carousel-group<?php if($g==0) echo ' active'; ?>" data-group="<?php echo $g; ?>">
<?php foreach ($group as $cat):
$img_id = get_term_meta($cat->term_id, 'thumbnail_id', true);
$img_url = $img_id ? wp_get_attachment_image_url($img_id, 'large') : wc_placeholder_img_src();
?>
<a href="<?php echo get_term_link($cat); ?>" class="cat-carousel-card">
<div class="cat-carousel-img-wrap">
<div class="cat-carousel-img" style="background-image:url('<?php echo esc_url($img_url); ?>')"></div>
</div>
<div class="cat-carousel-title"><?php echo esc_html($cat->name); ?></div>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<div class="cat-carousel-group-mobile">
<?php foreach ($cats as $cat):
$img_id = get_term_meta($cat->term_id, 'thumbnail_id', true);
$img_url = $img_id ? wp_get_attachment_image_url($img_id, 'large') : wc_placeholder_img_src();
?>
<a href="<?php echo get_term_link($cat); ?>" class="cat-carousel-card">
<div class="cat-carousel-img-wrap">
<div class="cat-carousel-img" style="background-image:url('<?php echo esc_url($img_url); ?>')"></div>
</div>
<div class="cat-carousel-title"><?php echo esc_html($cat->name); ?></div>
</a>
<?php endforeach; ?>
</div>
</div>
<button class="cat-carousel-btn prev" aria-label="Previous">
<svg fill="#000000" viewBox="0 0 24 24" id="left-circle-1" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line" width="44" height="44"><g><circle cx="12" cy="12" r="9" style="fill: #F05B2D; stroke-width: 2;"></circle><polyline points="13 9 10 12 13 15" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></polyline><circle cx="12" cy="12" r="9" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></circle></g></svg>
</button>
<button class="cat-carousel-btn next" aria-label="Next">
<svg fill="#000000" viewBox="0 0 24 24" id="right-circle" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line" width="44" height="44"><g><circle cx="12" cy="12" r="9" style="fill: #F05B2D; stroke-width: 2;"></circle><polyline points="11 15 14 12 11 9" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></polyline><circle cx="12" cy="12" r="9" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></circle></g></svg>
</button>
<div class="cat-carousel-progress">
<div class="cat-carousel-progress-bg"></div>
<div class="cat-carousel-progress-bar"></div>
</div>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Marcellus:wght@400&display=swap');
.cat-carousel-wrap { width:100%; margin:0 auto 24px auto; position:relative; box-sizing:border-box; padding:0; max-width:100vw;}
.cat-carousel-inner { position:relative; width:100%;}
.cat-carousel-group, .cat-carousel-group-mobile { display:none; }
.cat-carousel-group.active { display:flex; flex-wrap:nowrap; width:100%; animation:fadeIn .6s; justify-content:flex-start; align-items:stretch; gap:30px;}
.cat-carousel-card { flex:1 1 0; max-width:calc((100% - 60px)/3); min-width:0; background:#fff; text-decoration:none; color:#222; display:flex; flex-direction:column; align-items:flex-start; border-radius:0; overflow:hidden; margin:0; box-shadow:none !important; border:none; position:relative; transition:none;}
.cat-carousel-img-wrap { width:100%; aspect-ratio:3/2; position:relative; overflow:hidden; background:#f7f7f7;}
.cat-carousel-img { width:100%; height:100%; background-size:cover; background-position:center; background-repeat:no-repeat; transition:transform .42s cubic-bezier(.28,.62,.37,1.18), box-shadow .38s; will-change:transform; box-shadow:none;}
.cat-carousel-card:hover .cat-carousel-img { transform:scale(1.06) translateY(-4px); box-shadow:0 6px 38px #f05b2d44; z-index:2;}
.cat-carousel-title { padding:12px 0 6px 0; font-family:'Marcellus',serif !important; font-size:24px; color:#222; text-align:left; font-weight:400; width:100%; border-radius:0; letter-spacing:0; line-height:1.2; margin:0;}
.cat-carousel-btn, .cat-carousel-btn:active, .cat-carousel-btn:focus, .cat-carousel-btn:hover { background:transparent !important; box-shadow:none !important; border:none !important; outline:none !important;}
.cat-carousel-btn { position:absolute; top:50%; transform:translateY(-50%); z-index:5; width:54px; height:54px; border-radius:50%; cursor:pointer; opacity:0; pointer-events:none; transition:opacity .22s; display:flex; align-items:center; justify-content:center; padding:0; visibility:visible;}
.cat-carousel-btn.hidden { display:none !important;}
.cat-carousel-btn svg { background:none !important;}
.cat-carousel-btn.prev { left:-27px;}
.cat-carousel-btn.next { right:-27px;}
.cat-carousel-wrap:hover .cat-carousel-btn { opacity:1; pointer-events:auto;}
.cat-carousel-progress { width:100%; height:2px; margin:40px 0 0 0; position:relative; background:none; z-index:1; user-select:none; pointer-events:none;}
.cat-carousel-progress-bg { position:absolute; top:0; left:0; width:100%; height:100%; background:#D9DFD7; border-radius:3px; z-index:0;}
.cat-carousel-progress-bar { position:absolute; top:0; left:0; height:100%; width:0; background:#2E4D37; border-radius:3px; z-index:1; transition:width .35s cubic-bezier(.44,.67,.6,1.12);}
@keyframes fadeIn { from { opacity:0; transform:translateY(30px);} to { opacity:1; transform:none;}}
@media (max-width:700px) {
.cat-carousel-wrap { margin:0 auto 12px auto;}
.cat-carousel-group { display:none !important;}
.cat-carousel-group-mobile { display:flex !important; flex-direction:row; gap:10px; overflow-x:auto; overflow-y:hidden; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch; justify-content:flex-start; width:100%;}
.cat-carousel-card { flex:0 0 calc((100vw - 10px)/1.5); max-width:calc((100vw - 10px)/1.5); min-width:160px; scroll-snap-align:start;}
.cat-carousel-img-wrap { aspect-ratio:3/2;}
.cat-carousel-title { font-size:16px; padding:8px 0 4px 0;}
.cat-carousel-btn { display:none !important;}
.cat-carousel-group-mobile::-webkit-scrollbar { display:none; height:0;}
.cat-carousel-group-mobile { -ms-overflow-style:none; scrollbar-width:none;}
.cat-carousel-progress { margin:8px 0 0 0;}
}
</style>
<script>
(function(){
var wrap = document.currentScript.parentElement,
groups = wrap.querySelectorAll('.cat-carousel-group'),
groupMobile = wrap.querySelector('.cat-carousel-group-mobile'),
btnPrev = wrap.querySelector('.cat-carousel-btn.prev'),
btnNext = wrap.querySelector('.cat-carousel-btn.next'),
progressBar = wrap.querySelector('.cat-carousel-progress-bar');
var total = groups.length, current = 0;
function updateCarousel(idx){
groups.forEach(function(g, i){ g.classList.toggle('active', i === idx); });
if(progressBar) progressBar.style.width = ((idx+1)/total*100)+'%';
if(btnPrev) btnPrev.classList.toggle('hidden', idx===0);
if(btnNext) btnNext.classList.toggle('hidden', idx===total-1);
}
if(btnPrev) btnPrev.onclick = function(){ if(current>0) current--; updateCarousel(current);}
if(btnNext) btnNext.onclick = function(){ if(current<total-1) current++; updateCarousel(current);}
wrap.tabIndex = 0;
wrap.addEventListener('keydown', function(e){
if(e.key==="ArrowLeft") btnPrev && btnPrev.click();
if(e.key==="ArrowRight") btnNext && btnNext.click();
});
function isMobile(){ return window.innerWidth <= 700; }
function setMobileProgress(){
if(!isMobile() || !groupMobile || !progressBar) return;
var scrollL = groupMobile.scrollLeft, w = groupMobile.scrollWidth, vw = groupMobile.clientWidth;
var maxScroll = w-vw;
var percent = maxScroll>0 ? Math.min(scrollL/maxScroll,1) : 0;
if(scrollL<=1) percent = (vw/w);
progressBar.style.width = (percent*100)+'%';
}
if(groupMobile){
groupMobile.addEventListener('scroll', setMobileProgress, {passive:true});
window.addEventListener('resize', function(){
if(isMobile()) setMobileProgress();
else updateCarousel(current);
});
}
if(isMobile()) setMobileProgress(); else updateCarousel(current);
window.addEventListener('resize', function(){
if(isMobile()) setMobileProgress();
else updateCarousel(current);
});
})();
</script>
<?php
return ob_get_clean();
});