/* 商品列表改版前端行為 #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 += '' + alt + ''; }); 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);