/*
商品列表改版前端行為 #53328
1. plpPanel:篩選器/排序右側滑出面板、已套用條件標籤(商品列表、商品集合頁/標籤頁/搜尋結果)
2. 商品卡:顏色色票切換、圖片輪播與進度條(僅手機版)、無規格商品直接加入購物車(以上頁面+品牌館商品列表)
商品卡 HTML 由 config/class/prod/card.php 產生,面板 HTML 由 products/include/plp_panel.php 產生。
商品卡事件一律綁在 document 上,ajax 換頁/篩選重新載入的卡片也適用。
*/
// ==========================================================
// 篩選器/排序面板
// ==========================================================
var plpPanel = (function($){
var listObj = null;
//目前已套用的條件(#plpApplied 內的 .search-focus 元素,list.js Search() 依此組參數)
function getApplied(){
var list = [];
$('#plpApplied .search-focus').each(function(){
list.push({type: $(this).attr('type'), id: String($(this).attr('search_id'))});
});
return list;
}
function setApplied(list){
var html = '';
$.each(list, function(i, item){
html += '';
});
$('#plpApplied').html(html);
}
//面板勾選狀態還原成已套用的條件(未按套用就關閉時,勾選不生效)
function syncInputs(){
var applied = getApplied();
$('#plpFilterPanel .plp-option-input').each(function(){
var input = $(this),
checked = false;
$.each(applied, function(i, item){
if(item.type === input.attr('data-type') && item.id === String(input.val())) checked = true;
});
input.prop('checked', checked);
});
}
//已套用條件標籤,每個標籤可單獨移除
function renderTags(){
var html = '';
$.each(getApplied(), function(i, item){
var input = $('#plpFilterPanel .plp-option-input[data-type="' + item.type + '"][value="' + item.id + '"]');
if(!input.length) return;
html += '';
});
$('#plpTags').html(html).toggleClass('hide', html === '');
}
function open(target){
syncInputs();
$(target).addClass('in-active').attr('aria-hidden', 'false');
$('body').addClass('overflow-hidden');
}
function close(){
$('.plp-panel.in-active').removeClass('in-active').attr('aria-hidden', 'true');
$('body').removeClass('overflow-hidden');
}
//重新載入商品列表並回到第 1 頁
function search(){
renderTags();
if(listObj) listObj.Search(1);
}
function init(obj){
listObj = obj;
$(document).on('click', '[data-plp-open]', function(e){
e.preventDefault();
open($(this).attr('data-plp-open'));
});
$(document).on('click', '[data-plp-close]', function(e){
e.preventDefault();
close();
});
$(document).on('keyup', function(e){
if(e.key === 'Escape') close();
});
//每組可收合
$(document).on('click', '.plp-group-title', function(){
$(this).closest('.plp-group').toggleClass('in-active');
});
//套用篩選
$(document).on('click', '.plp-btn-apply', function(){
var list = [];
$('#plpFilterPanel .plp-option-input:checked').each(function(){
list.push({type: $(this).attr('data-type'), id: String($(this).val())});
});
setApplied(list);
close();
search();
});
//清除篩選:清空全部條件並回到未篩選狀態
$(document).on('click', '.plp-btn-clear', function(){
$('#plpFilterPanel .plp-option-input').prop('checked', false);
setApplied([]);
close();
search();
});
//移除單一條件
$(document).on('click', '.plp-tag', function(){
var type = $(this).attr('data-type'),
id = String($(this).attr('data-id'));
setApplied($.grep(getApplied(), function(item){
return !(item.type === type && item.id === id);
}));
syncInputs();
search();
});
//排序:點選即套用
$(document).on('click', '.plp-sort-link', function(){
$('.plp-sort-link').removeClass('in-active');
$(this).addClass('in-active');
$('#orderbox').val($(this).attr('data-value'));
close();
if(listObj) listObj.Search(1);
});
renderTags();
}
return {
init: init,
};
})(jQuery);
// ==========================================================
// 商品卡:圖片輪播、色票切換、直接加入購物車
// ==========================================================
;(function($){
//輪播只在 md 以下(手機)提供,以手指滑動切換;md 以上只顯示第 1 張、不輪播(其餘張與進度條由 CSS 隱藏)
//斷點與 CSS mediaBreakpointDown('md') 相同;每次事件當下判斷,視窗縮放後即時生效
function isMobile(){
return !!(window.matchMedia && window.matchMedia('(max-width: 767.98px)').matches);
}
function slideTo(card, index){
var slides = card.find('.card-slide'),
count = slides.length;
if(count <= 1) return;
finishSettle(card.find('.card-slides'));
index = ((index % count) + count) % count;
slides.removeClass('in-active').eq(index).addClass('in-active');
card.find('.card-progress-item').removeClass('in-active').eq(index).addClass('in-active');
card.data('plpSlide', index);
}
//點進度條任一段直接跳到該張(進度條僅手機版顯示)
$(document).on('click', '.card-progress-item', function(e){
e.preventDefault();
var card = $(this).closest('.product-box');
slideTo(card, parseInt($(this).attr('data-index')) || 0);
});
//手機版手勢(觸控滑動與滑鼠拖曳共用):圖片跟著手指水平移動,鄰張同步從側邊滑入;
//放開時水平位移達 SWIPE_MIN 就以滑動動畫帶到下一張/上一張,不足則彈回原位
//位移量依手指位置即時計算,只能以行內 transform 設定;其餘狀態用 class(product_list_plp.styl):
//.is-dragging 拖曳中關閉淡入淡出、.is-peek 顯示滑入的鄰張、.is-settling 放開後的位移動畫
var SWIPE_MIN = 30, //放開時水平位移達此距離才換張
AXIS_LOCK = 6, //位移達此距離時判定這次手勢是水平(拖圖片)或垂直(捲動頁面),之後不再改判
SLIDE_DURATION = 300; //放開後的位移動畫時間,須與 .is-settling 的 transition 一致
//擺放目前這張與鄰張:目前這張位移 x,toNext 時下一張接在右側、否則上一張接在左側
function placeSlides(box, x, toNext){
var slides = box.find('.card-slide'),
count = slides.length,
cur = box.closest('.product-box').data('plpSlide') || 0,
width = box[0].offsetWidth,
side = toNext ? (cur + 1) % count : (cur - 1 + count) % count,
sideX = toNext ? x + width : x - width;
slides.each(function(i){
var pos = (i === cur) ? x : ((i === side) ? sideX : null);
this.style.transform = (pos === null) ? '' : 'translateX(' + pos + 'px)';
$(this).toggleClass('is-peek', i === side);
});
}
//拖曳中:先判定手勢方向,水平才讓圖片跟著移動;回傳 true 表示這次是水平拖曳(呼叫端需取消預設捲動)
function dragMove(box, drag, dx, dy){
if(!drag.lock){
if(Math.abs(dx) < AXIS_LOCK && Math.abs(dy) < AXIS_LOCK) return false;
drag.lock = (Math.abs(dx) > Math.abs(dy)) ? 'x' : 'y';
if(drag.lock === 'x') box.addClass('is-dragging');
}
if(drag.lock !== 'x') return false;
drag.dx = dx;
placeSlides(box, dx, dx < 0);
return true;
}
//動畫結束:清掉位移、正式切換 in-active;切換當下暫時關閉淡入淡出,舊圖才不會在原位淡出閃一下
function finishSettle(box){
var settle = box.data('plpSettle');
if(!settle) return;
clearTimeout(settle.timer);
box.removeData('plpSettle').addClass('is-dragging').removeClass('is-settling');
box.find('.card-slide').css('transform', '').removeClass('is-peek');
if(settle.step) slideTo(box.closest('.product-box'), settle.cur + settle.step);
box[0].offsetWidth; //強制重排,讓上面的切換在無動畫下生效後才恢復淡入淡出
box.removeClass('is-dragging');
}
//放開:dx 為最後的水平位移,toNext 為放開當下露出的是哪一側(彈回時鄰張要往同一側退回)
//水平拖曳一定會帶出 click 的風險(滑鼠放開必觸發),所以一律擋掉這次放開帶出的 click(不連到商品頁);
//旗標若留著會吞掉下一次正常點擊(滑完再點圖片沒反應),所以新的觸控/拖曳開始時先清除,並在短時間後自動失效
var SWIPE_CLICK_GUARD = 400;
function swipeSlides(box, dx, toNext){
var step = (Math.abs(dx) >= SWIPE_MIN) ? ((dx < 0) ? 1 : -1) : 0;
box.removeClass('is-dragging').addClass('is-settling');
placeSlides(box, -step * box[0].offsetWidth, toNext);
box.data('plpSettle', {
step: step,
cur: box.closest('.product-box').data('plpSlide') || 0,
timer: setTimeout(function(){
finishSettle(box);
}, SLIDE_DURATION),
});
var link = box.closest('a').data('plpSwiped', true);
setTimeout(function(){
link.removeData('plpSwiped');
}, SWIPE_CLICK_GUARD);
}
//觸控/拖曳事件綁在外層連結 a.productImg,不可綁在 .card-slides:
//同一連結內的活動角標 .sale-img 是蓋滿整張圖的絕對定位層(z-index:2,沒有角標圖時也一樣蓋著),
//手指/滑鼠實際按到的是它;它與 .card-slides 是兄弟節點,綁在 .card-slides 的事件永遠不會觸發
function slideBox(el){
if(!isMobile()) return $();
var box = $(el).closest('.productImg').find('.card-slides');
return (box.find('.card-slide').length > 1) ? box : $();
}
//觸控:左右滑動切換(觸控平板也適用)
$(document).on('touchstart', '.productImg', function(e){
var box = slideBox(this),
touch = e.originalEvent.touches[0];
if(!box.length || !touch) return;
finishSettle(box);
box.data('plpTouch', {x: touch.clientX, y: touch.clientY, lock: null, dx: 0});
$(this).removeData('plpSwiped');
});
//水平拖曳時圖片跟著手指移動,並取消瀏覽器預設手勢:否則瀏覽器會把這次手勢拿去捲動/拖曳外層連結,改送 touchcancel、收不到 touchend,
//結果就是滑了不換張、連結被反白選取;判定為垂直的手勢整段不攔,頁面照常上下捲動。
//須以原生 addEventListener 綁 passive:false,jQuery 綁在 document 的 touchmove 會被瀏覽器視為 passive,preventDefault 無效
document.addEventListener('touchmove', function(e){
var box = slideBox(e.target),
start = box.data('plpTouch'),
touch = e.touches[0];
if(!box.length || !start || !touch || !e.cancelable) return;
if(!dragMove(box, start, touch.clientX - start.x, touch.clientY - start.y)) return;
e.preventDefault();
}, {passive: false});
$(document).on('touchend', '.productImg', function(e){
var box = slideBox(this),
start = box.data('plpTouch'),
touch = e.originalEvent.changedTouches[0];
box.removeData('plpTouch');
if(!start || start.lock !== 'x' || !touch) return;
var dx = touch.clientX - start.x;
swipeSlides(box, dx, dx < 0);
});
//手勢被瀏覽器中斷:拖到一半的圖片彈回原位
$(document).on('touchcancel', '.productImg', function(){
var box = slideBox(this),
start = box.data('plpTouch');
box.removeData('plpTouch');
if(start && start.lock === 'x') swipeSlides(box, 0, start.dx < 0);
});
//滑鼠(僅手機版 md 以下):按住圖片左右拖曳切換,停在拖到的那張
//拖曳中與放開時游標可能已在圖片外,所以 mousemove/mouseup 綁在 document
var mouseDrag = null;
$(document).on('mousedown', '.productImg', function(e){
var box = slideBox(this);
//只在手機版(md 以下)開放拖曳;電腦版只顯示單圖
if(!isMobile() || e.which !== 1 || !box.length) return;
//取消預設動作:否則瀏覽器會開始拖曳外層連結 a.productImg(或反白選取),之後收不到 mouseup;
//mousedown 取消預設不影響 click,單純點擊仍會進商品頁
e.preventDefault();
finishSettle(box);
mouseDrag = {box: box, x: e.clientX, y: e.clientY, lock: null, dx: 0};
$(this).removeData('plpSwiped');
});
$(document).on('mousemove', function(e){
if(mouseDrag) dragMove(mouseDrag.box, mouseDrag, e.clientX - mouseDrag.x, e.clientY - mouseDrag.y);
});
//沒有水平拖曳過(單純點擊或垂直移動)不處理,click 照常連到商品頁
$(document).on('mouseup', function(e){
if(!mouseDrag) return;
var drag = mouseDrag;
mouseDrag = null;
drag.box.removeClass('is-dragging');
if(drag.lock === 'x') swipeSlides(drag.box, e.clientX - drag.x, e.clientX - drag.x < 0);
});
//第二道防線:原生拖曳的起點可能是圖片或外層連結 a.productImg,兩者都取消,否則會拖出殘影、收不到 mouseup
$(document).on('dragstart', '.productImg', function(e){
if(isMobile() && $(this).find('.card-slides').length) e.preventDefault();
});
//以捕獲階段攔截:滑鼠拖曳放開一定會帶出 click,除了不換頁,也要擋住直接綁在 .setDataLayer 上的 select_item 事件(sgm.js)
document.addEventListener('click', function(e){
var link = $(e.target).closest('.productImg');
if(link.length && link.data('plpSwiped')){
e.preventDefault();
e.stopPropagation();
link.removeData('plpSwiped');
}
}, true);
//重建輪播圖與進度條
function renderSlides(card, d){
var box = card.find('.card-slides'),
prefix = box.attr('data-prefix') || 'L_',
alt = box.find('img').first().attr('alt') || '',
pics = d.Card_Pics || [];
if(!box.length || !pics.length) return;
finishSettle(box);
var html = '';
$.each(pics, function(i, pic){
html += '
';
});
box.html(html).attr('data-count', pics.length);
var progress = '';
if(pics.length > 1){
progress = '';
$.each(pics, function(i){
progress += '';
});
progress += '';
}
card.find('.card-progress').remove();
if(progress) card.find('.productImg').first().after(progress);
card.data('plpSlide', 0);
}
//點色票:主圖、價格、售完標籤即時換成該顏色,不跳頁;卡片連結同步改為該顏色的商品頁
var swatchLoading = false;
$(document).on('click', '.card-swatch[SID]', function(e){
e.preventDefault();
var btn = $(this),
card = btn.closest('.product-box'),
list = btn.closest('.card-swatches');
if(swatchLoading || btn.hasClass('in-choice')) return;
swatchLoading = true;
$.ajax({
url: Project_Country + 'products/ajax/common/ajax_get_prod_item.php',
type: 'POST',
dataType: 'json',
data: {SID: btn.attr('SID'), Type: '1'},
}).done(function(d){
list.find('.card-swatch').removeClass('in-choice');
btn.addClass('in-choice');
//價格(保留原本的特價紅字樣式)
var priceBox = card.find('.price-wrapper'),
insClass = priceBox.find('ins').attr('class');
if(priceBox.length){
priceBox.html(d.Card_Price);
if(insClass) priceBox.find('ins').attr('class', insClass);
}
renderSlides(card, d);
//售完等狀態標籤
if(d.Status_String) card.find('.prod-status-string').replaceWith(d.Status_String);
//商品頁連結
var oldSerial = String(list.attr('data-serial')),
newSerial = String(btn.attr('data-serial'));
if(oldSerial && newSerial && oldSerial !== newSerial){
card.find('a[href*="/product-detail/"]').each(function(){
var href = $(this).attr('href');
$(this).attr('href', href.split('/product-detail/' + oldSerial).join('/product-detail/' + newSerial));
});
list.attr('data-serial', newSerial);
}
//快速加入購物車與喜愛清單改為該顏色
card.find('.card-quick-add').attr('SID', d.ID).toggleClass('hide', !d.Can_Quick_Add);
card.find('.product-love').attr('sid', d.ID);
}).fail(function(x){
console.log(x);
}).always(function(){
swatchLoading = false;
});
});
//無規格商品:直接加入 1 件,不開跳窗
var addLoading = false;
$(document).on('click', '.card-quick-direct', function(e){
e.preventDefault();
var SID = $(this).attr('SID');
if(addLoading || !SID) return;
addLoading = true;
$.ajax({
url: Project_Country + 'ajax/ajax_add_to_cart.php',
type: 'POST',
dataType: 'json',
cache: false,
data: 'SID=' + SID + '&num=1&Type=normal&webcacheno=' + ($('#webcacheno').val() || ''),
}).done(function(d){
if(d.Alert != 'OK'){
alert(d.Alert);
return;
}
$('.cart-total').html(d.Total);
if(d.DataLayer){
eval(d.DataLayer);
}
FloatCartPopup({Type: 'Prod', ID: SID, Act: 'Add'});
AlertPop({
icon: '',
string: _jsLang.加入購物車成功,
duration: 2000,
});
}).fail(function(){
alert('網路連線過慢,網頁請重新整理');
}).always(function(){
addLoading = false;
});
});
})(jQuery);