1Tạo 2 TAB cố định
<div class="swipe-tabs-wrapper">
<div class="tab-header">
<button class="tab-btn active" data-tab="0">TAB 1</button>
<button class="tab-btn" data-tab="1">TAB 2</button>
<div class="tab-indicator"></div>
</div>
<!-- Vùng vuốt nội dung -->
<div class="tab-viewport">
<div class="tab-container">
<div class="tab-content">
<div class="tab-inner-content">
<!-- Nội dung TAB 1 -->
</div>
</div>
<div class="tab-content">
<div class="tab-inner-content">
<!-- Nội dung TAB 2 -->
</div>
</div>
</div></div></div>
/**
* 2 TAB VUỐT SWIPE MƯỢT MÀ CHO THEME FLATSOME (PHIÊN BẢN BỌC WP_HEAD & WP_FOOTER CHẠY THEO DIV)
*/
// 1. Bọc và chèn CSS vào wp_head để tránh bị cache/gộp file làm mất style
add_action('wp_head', 'flatsome_swipe_tabs_render_css', 999);
function flatsome_swipe_tabs_render_css() {
?>
<style id="flatsome-swipe-tabs-css">
.swipe-tabs-wrapper {
width: 100%;
display: flex;
flex-direction: column;
background: #fff;
overflow: hidden;
margin-bottom: 20px;
}
.swipe-tabs-wrapper .tab-header {
display: flex;
background-color: #ffffff;
border-bottom: 1px solid #ececec;
position: relative;
z-index: 10;
}
.swipe-tabs-wrapper .tab-btn {
flex: 1;
padding: 15px 0;
border: none !important;
background: none !important;
font-size: 15px;
font-weight: bold;
text-transform: uppercase;
color: #999;
cursor: pointer;
text-align: center;
box-shadow: none !important;
margin: 0 !important;
transition: color 0.3s ease;
-webkit-tap-highlight-color: transparent;
}
.swipe-tabs-wrapper .tab-btn.active {
color: #446084; /* Thay đổi màu chữ active của bạn tại đây */
}
.swipe-tabs-wrapper .tab-indicator {
position: absolute;
bottom: 0;
left: 0;
width: 50%;
height: 3px;
background-color: #446084; /* Thay đổi màu thanh gạch dưới tại đây */
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.swipe-tabs-wrapper .tab-viewport {
width: 100%;
overflow: hidden;
position: relative;
}
.swipe-tabs-wrapper .tab-container {
display: flex;
width: 200%;
will-change: transform;
}
.swipe-tabs-wrapper .tab-content {
width: 50%;
box-sizing: border-box;
}
.swipe-tabs-wrapper .tab-inner-content {
padding: 20px 10px;
}
</style>
<?php
}
// 2. Bọc và chèn Script vào wp_footer để đảm bảo chạy mượt mà không bị nuốt code
add_action('wp_footer', 'flatsome_swipe_tabs_render_js', 999);
function flatsome_swipe_tabs_render_js() {
?>
<script id="flatsome-swipe-tabs-js">
document.addEventListener('DOMContentLoaded', function() {
// Tìm tất cả các cụm tab dạng div trên trang
var wrappers = document.querySelectorAll('.swipe-tabs-wrapper');
wrappers.forEach(function(wrapper) {
var container = wrapper.querySelector('.tab-container');
var viewport = wrapper.querySelector('.tab-viewport');
var indicator = wrapper.querySelector('.tab-indicator');
var buttons = wrapper.querySelectorAll('.tab-btn');
if (!container || !viewport) return;
var currentIdx = 0;
var startX = 0;
var startY = 0;
var currentTranslate = 0;
var prevTranslate = 0;
var isDragging = false;
var isScrollingVertical = false;
var width = viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
function updateWidth() {
width = viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
switchTab(currentIdx);
}
window.addEventListener('resize', updateWidth);
viewport.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
isDragging = true;
isScrollingVertical = false;
container.style.transition = 'none';
indicator.style.transition = 'none';
}, { passive: true });
viewport.addEventListener('touchmove', function(e) {
if (!isDragging) return;
var currentX = e.touches[0].clientX;
var currentY = e.touches[0].clientY;
var diffX = currentX - startX;
var diffY = currentY - startY;
if (!isScrollingVertical && Math.abs(diffY) > Math.abs(diffX)) {
isScrollingVertical = true;
isDragging = false;
return;
}
if (isScrollingVertical) return;
if (e.cancelable) e.preventDefault();
currentTranslate = prevTranslate + diffX;
if (currentTranslate > 0) currentTranslate = diffX * 0.25;
if (currentTranslate < -width) currentTranslate = -width + (diffX * 0.25);
container.style.transform = 'translateX(' + currentTranslate + 'px)';
var indicatorProgress = (-currentTranslate / width) * 100;
indicator.style.transform = 'translateX(' + indicatorProgress + '%)';
}, { passive: false });
viewport.addEventListener('touchend', function() {
if (!isDragging) return;
isDragging = false;
var movedBy = currentTranslate - prevTranslate;
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
if (movedBy < -width * 0.2 && currentIdx < 1) {
currentIdx++;
} else if (movedBy > width * 0.2 && currentIdx > 0) {
currentIdx--;
}
switchTab(currentIdx);
});
buttons.forEach(function(btn) {
btn.addEventListener('click', function() {
var index = parseInt(this.getAttribute('data-tab'));
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
switchTab(index);
});
});
function switchTab(index) {
currentIdx = index;
prevTranslate = -currentIdx * width;
container.style.transform = 'translateX(' + prevTranslate + 'px)';
if (indicator) {
indicator.style.transform = 'translateX(' + (currentIdx * 100) + '%)';
}
buttons.forEach(function(btn, i) {
if (i === index) btn.classList.add('active');
else btn.classList.remove('active');
});
}
// Kích hoạt tính toán lại kích thước sau khi DOM tải xong 100ms
setTimeout(updateWidth, 100);
});
});
</script>
<?php
}
1Tạo 3 TAB cố định
<div class="swipe-tabs-wrapper">
<!-- Thanh tiêu đề chuyển 3 Tab -->
<div class="tab-header">
<button class="tab-btn active" data-tab="0">Mô tả sản phẩm</button>
<button class="tab-btn" data-tab="1">Đánh giá</button>
<button class="tab-btn" data-tab="2">Chính sách bảo hành</button> <!-- Thêm nút Tab 3 -->
<div class="tab-indicator"></div>
</div>
<!-- Vùng vuốt nội dung -->
<div class="tab-viewport">
<div class="tab-container">
<!-- Nội dung TAB 1 -->
<div class="tab-content">
<div class="tab-inner-content">
<h3>Thông tin chi tiết</h3>
<p>Nội dung của Tab 1 viết ở đây. Bạn có thể sử dụng các thẻ HTML cơ bản như hình ảnh, danh sách gạch đầu dòng...</p>
</div>
</div>
<!-- Nội dung TAB 2 -->
<div class="tab-content">
<div class="tab-inner-content">
<h3>Đánh giá từ khách hàng</h3>
<p>Nội dung của Tab 2 viết ở đây. Hệ thống Script tự động nhận diện cấu trúc class của div để kích hoạt tính năng vuốt.</p>
</div>
</div>
<!-- Nội dung TAB 3 -->
<div class="tab-content">
<div class="tab-inner-content">
<h3>Chính sách & Đổi trả</h3>
<p>Nội dung của Tab 3 viết ở đây. Cam kết hàng chính hãng, hỗ trợ đổi trả miễn phí trong vòng 7 ngày nếu có lỗi từ nhà sản xuất.</p>
</div>
</div>
</div>
</div>
</div>
/**
* 3 TAB VUỐT SWIPE MƯỢT MÀ CHO THEME FLATSOME (CHẠY THEO THẺ DIV)
*/
// 1. Bọc và chèn CSS vào wp_head (Cập nhật tỷ lệ cho 3 Tab)
add_action('wp_head', 'flatsome_swipe_tabs_render_css', 999);
function flatsome_swipe_tabs_render_css() {
?>
<style id="flatsome-swipe-tabs-css">
.swipe-tabs-wrapper {
width: 100%;
display: flex;
flex-direction: column;
background: #fff;
overflow: hidden;
margin-bottom: 20px;
}
.swipe-tabs-wrapper .tab-header {
display: flex;
background-color: #ffffff;
border-bottom: 1px solid #ececec;
position: relative;
z-index: 10;
}
.swipe-tabs-wrapper .tab-btn {
flex: 1;
padding: 15px 5px;
border: none !important;
background: none !important;
font-size: 14px; /* Giảm nhẹ font-size để vừa khít 3 tab trên mobile */
font-weight: bold;
text-transform: uppercase;
color: #999;
cursor: pointer;
text-align: center;
box-shadow: none !important;
margin: 0 !important;
transition: color 0.3s ease;
-webkit-tap-highlight-color: transparent;
}
.swipe-tabs-wrapper .tab-btn.active {
color: #446084;
}
.swipe-tabs-wrapper .tab-indicator {
position: absolute;
bottom: 0;
left: 0;
width: 33.333%; /* Thay đổi thành 1/3 chiều rộng thanh tab */
height: 3px;
background-color: #446084;
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.swipe-tabs-wrapper .tab-viewport {
width: 100%;
overflow: hidden;
position: relative;
}
.swipe-tabs-wrapper .tab-container {
display: flex;
width: 300%; /* 3 tab = 300% tổng chiều rộng */
will-change: transform;
}
.swipe-tabs-wrapper .tab-content {
width: 33.333%; /* Mỗi nội dung tab chiếm 1/3 không gian của 300% */
box-sizing: border-box;
}
.swipe-tabs-wrapper .tab-inner-content {
padding: 20px 10px;
}
</style>
<?php
}
// 2. Bọc và chèn Script vào wp_footer (Cập nhật giới hạn index vuốt cho 3 Tab)
add_action('wp_footer', 'flatsome_swipe_tabs_render_js', 999);
function flatsome_swipe_tabs_render_js() {
?>
<script id="flatsome-swipe-tabs-js">
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.swipe-tabs-wrapper');
wrappers.forEach(function(wrapper) {
var container = wrapper.querySelector('.tab-container');
var viewport = wrapper.querySelector('.tab-viewport');
var indicator = wrapper.querySelector('.tab-indicator');
var buttons = wrapper.querySelectorAll('.tab-btn');
if (!container || !viewport) return;
var currentIdx = 0;
var startX = 0;
var startY = 0;
var currentTranslate = 0;
var prevTranslate = 0;
var isDragging = false;
var isScrollingVertical = false;
var width = viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
function updateWidth() {
width = viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
switchTab(currentIdx);
}
window.addEventListener('resize', updateWidth);
viewport.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
isDragging = true;
isScrollingVertical = false;
container.style.transition = 'none';
if(indicator) indicator.style.transition = 'none';
}, { passive: true });
viewport.addEventListener('touchmove', function(e) {
if (!isDragging) return;
var currentX = e.touches[0].clientX;
var currentY = e.touches[0].clientY;
var diffX = currentX - startX;
var diffY = currentY - startY;
if (!isScrollingVertical && Math.abs(diffY) > Math.abs(diffX)) {
isScrollingVertical = true;
isDragging = false;
return;
}
if (isScrollingVertical) return;
if (e.cancelable) e.preventDefault();
currentTranslate = prevTranslate + diffX;
// Cập nhật kháng lực biên dựa trên chiều rộng nhân với số lượng tab (biên tối đa là -2 * width)
if (currentTranslate > 0) currentTranslate = diffX * 0.25;
if (currentTranslate < -width * 2) currentTranslate = (-width * 2) + (diffX * 0.25);
container.style.transform = 'translateX(' + currentTranslate + 'px)';
if (indicator) {
// Thanh indicator di chuyển tương ứng theo tỷ lệ kéo ngón tay thực tế
var indicatorProgress = (-currentTranslate / width) * 100;
indicator.style.transform = 'translateX(' + indicatorProgress + '%)';
}
}, { passive: false });
viewport.addEventListener('touchend', function() {
if (!isDragging) return;
isDragging = false;
var movedBy = currentTranslate - prevTranslate;
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
if (indicator) indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
// Kiểm tra giới hạn vuốt đến tối đa Tab thứ 3 (index = 2)
if (movedBy < -width * 0.2 && currentIdx < 2) {
currentIdx++;
} else if (movedBy > width * 0.2 && currentIdx > 0) {
currentIdx--;
}
switchTab(currentIdx);
});
buttons.forEach(function(btn) {
btn.addEventListener('click', function() {
var index = parseInt(this.getAttribute('data-tab'));
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
if (indicator) indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
switchTab(index);
});
});
function switchTab(index) {
currentIdx = index;
prevTranslate = -currentIdx * width;
container.style.transform = 'translateX(' + prevTranslate + 'px)';
if (indicator) {
indicator.style.transform = 'translateX(' + (currentIdx * 100) + '%)';
}
buttons.forEach(function(btn, i) {
if (i === index) btn.classList.add('active');
else btn.classList.remove('active');
});
}
setTimeout(updateWidth, 150);
});
});
</script>
<?php
}
3Tạo TAB tùy chỉnh
<div class="swipe-tabs-wrapper">
<div class="tab-header">
<button class="tab-btn active" data-tab="0">Mô tả</button>
<button class="tab-btn" data-tab="1">Đánh giá</button>
<button class="tab-btn" data-tab="2">Đánh giá</button>
<div class="tab-indicator"></div>
</div>
<div class="tab-viewport">
<div class="tab-container">
<div class="tab-content"><div class="tab-inner-content">Nội dung 1...</div></div>
<div class="tab-content"><div class="tab-inner-content">Nội dung 2...</div></div>
<div class="tab-content"><div class="tab-inner-content">Nội dung 3...</div></div>
</div>
</div>
</div>
/**
* FIXED: HỆ THỐNG VUỐT TỰ ĐỘNG NHẬN DIỆN SỐ LƯỢNG TAB (FIX LỖI MẤT CHỨC NĂNG SWIPE)
*/
// 1. Chèn CSS khung cố định vào wp_head
add_action('wp_head', 'flatsome_flexible_swipe_tabs_css', 999);
function flatsome_flexible_swipe_tabs_css() {
?>
<style id="flatsome-flexible-swipe-tabs-css">
.swipe-tabs-wrapper {
width: 100%;
display: flex;
flex-direction: column;
background: #fff;
overflow: hidden;
margin-bottom: 20px;
}
.swipe-tabs-wrapper .tab-header {
display: flex;
background-color: #ffffff;
border-bottom: 1px solid #ececec;
position: relative;
z-index: 10;
}
.swipe-tabs-wrapper .tab-btn {
flex: 1;
padding: 15px 5px;
border: none !important;
background: none !important;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
color: #999;
cursor: pointer;
text-align: center;
box-shadow: none !important;
margin: 0 !important;
transition: color 0.3s ease;
-webkit-tap-highlight-color: transparent;
}
.swipe-tabs-wrapper .tab-btn.active {
color: #446084;
}
.swipe-tabs-wrapper .tab-indicator {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background-color: #446084;
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.swipe-tabs-wrapper .tab-viewport {
width: 100%;
overflow: hidden;
position: relative;
touch-action: pan-y; /* Ép trình duyệt cho phép cuộn dọc trang thoải mái nhưng không chặn vuốt ngang */
}
.swipe-tabs-wrapper .tab-container {
display: flex;
flex-direction: row !important;
flex-wrap: nowrap !important; /* Không cho phép các tab rớt hàng */
will-change: transform;
transform: translateX(0px);
}
.swipe-tabs-wrapper .tab-content {
flex-shrink: 0 !important;
flex-grow: 0 !important;
box-sizing: border-box;
}
.swipe-tabs-wrapper .tab-inner-content {
padding: 20px 10px;
}
</style>
<?php
}
// 2. Chèn Script bám ngón tay thời gian thực (Real-time tracking) vào wp_footer
add_action('wp_footer', 'flatsome_flexible_swipe_tabs_js', 999);
function flatsome_flexible_swipe_tabs_js() {
?>
<script id="flatsome-flexible-swipe-tabs-js">
document.addEventListener('DOMContentLoaded', function() {
function initAllFlexibleTabs() {
var wrappers = document.querySelectorAll('.swipe-tabs-wrapper');
wrappers.forEach(function(wrapper) {
var container = wrapper.querySelector('.tab-container');
var viewport = wrapper.querySelector('.tab-viewport');
var indicator = wrapper.querySelector('.tab-indicator');
var buttons = wrapper.querySelectorAll('.tab-btn');
var contents = wrapper.querySelectorAll('.tab-content');
if (!container || !viewport || !buttons.length || !contents.length) return;
var totalTabs = contents.length;
var currentIdx = 0;
var startX = 0;
var startY = 0;
var currentTranslate = 0;
var prevTranslate = 0;
var isDragging = false;
var isScrollingVertical = false;
// Lấy độ rộng chuẩn xác thực tế của khung bọc bên ngoài
var width = viewport.getBoundingClientRect().width || viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
// Hàm cập nhật kích thước layout động theo số lượng tab
function setupLayout() {
width = viewport.getBoundingClientRect().width || viewport.clientWidth || wrapper.offsetWidth || window.innerWidth;
// Ép trực tiếp thuộc tính pixel thay vì dùng % để tránh lỗi hiển thị của Flatsome
container.style.width = (width * totalTabs) + 'px';
if (indicator) indicator.style.width = (width / totalTabs) + 'px';
contents.forEach(function(content) {
content.style.width = width + 'px';
});
switchTab(currentIdx, false);
}
// Lắng nghe thay đổi kích thước màn hình
window.addEventListener('resize', setupLayout);
if (window.ResizeObserver) {
var ro = new ResizeObserver(function() { setupLayout(); });
ro.observe(viewport);
}
// Thiết lập layout lần đầu tiên
setupLayout();
viewport.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
isDragging = true;
isScrollingVertical = false;
container.style.transition = 'none';
if (indicator) indicator.style.transition = 'none';
}, { passive: true });
viewport.addEventListener('touchmove', function(e) {
if (!isDragging) return;
var currentX = e.touches[0].clientX;
var currentY = e.touches[0].clientY;
var diffX = currentX - startX;
var diffY = currentY - startY;
// Nếu phát hiện hành vi cuộn trang đi xuống, tắt tính năng vuốt ngang ngay lập tức
if (!isScrollingVertical && Math.abs(diffY) > Math.abs(diffX)) {
isScrollingVertical = true;
isDragging = false;
return;
}
if (isScrollingVertical) return;
// Chặn hành vi giật trang của trình duyệt khi đang vuốt tab chuẩn xác
if (e.cancelable) e.preventDefault();
currentTranslate = prevTranslate + diffX;
// Kháng lực biên lò xo
var maxTranslate = -width * (totalTabs - 1);
if (currentTranslate > 0) currentTranslate = diffX * 0.25;
if (currentTranslate < maxTranslate) currentTranslate = maxTranslate + (diffX * 0.25);
container.style.transform = 'translateX(' + currentTranslate + 'px)';
if (indicator) {
var indicatorProgress = (-currentTranslate / width) * 100;
indicator.style.transform = 'translateX(' + indicatorProgress + '%)';
}
}, { passive: false });
viewport.addEventListener('touchend', function() {
if (!isDragging) return;
isDragging = false;
var movedBy = currentTranslate - prevTranslate;
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
if (indicator) indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
// Đủ lực vuốt qua 20% màn hình sẽ chuyển tab
if (movedBy < -width * 0.2 && currentIdx < totalTabs - 1) {
currentIdx++;
} else if (movedBy > width * 0.2 && currentIdx > 0) {
currentIdx--;
}
switchTab(currentIdx, true);
});
buttons.forEach(function(btn) {
btn.addEventListener('click', function() {
var index = parseInt(this.getAttribute('data-tab'));
container.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
if (indicator) indicator.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
switchTab(index, true);
});
});
function switchTab(index, hasTransition) {
currentIdx = index;
prevTranslate = -currentIdx * width;
if (!hasTransition) {
container.style.transition = 'none';
if (indicator) indicator.style.transition = 'none';
}
container.style.transform = 'translateX(' + prevTranslate + 'px)';
if (indicator) {
// Tính toán chính xác vị trí gạch chân theo tỷ lệ số lượng tab
indicator.style.transform = 'translateX(' + (currentIdx * 100) + '%)';
}
buttons.forEach(function(btn, i) {
if (i === index) btn.classList.add('active');
else btn.classList.remove('active');
});
}
});
}
// Kích hoạt chạy an toàn
initAllFlexibleTabs();
window.addEventListener('load', function() { setTimeout(initAllFlexibleTabs, 200); });
if (window.jQuery) {
jQuery(document).on('ux_builder_rendered', function() { setTimeout(initAllFlexibleTabs, 300); });
}
});
</script>
<?php
}