商品屬性標籤頁

Introduction

若您的商家SHOPLINE 商家有與 awoo 合作使用 商品屬性標籤功能,SHOPLINE 現在有提供原生的屬性標籤頁面來提供給商家進行整合,促進 SEO 流量增長

修改步驟

步驟一:確認套用的 theme 是否有支援此功能

原生的商品屬性標籤頁僅支援以下的 theme

  • UltraChic
  • Kingsman
  • Varm
  • Philia
  • Bianco
  • Sangria

步驟二:新增程式碼

以下是您需要新增程式碼的操作步驟,請依照描述和示例,找到指定檔案後新增程式碼:



檔案名稱:theme.css.liquid

  1. 請到 theme.css.liquid 新增屬性標籤頁所需的樣式代碼,如以下內容
.tag-products__title {
    font-size: calc(20px * var(--font-size-title, 1));
    font-family: var(--font-family-title);
    float: none
}

.tag-products__page-description {
    display: flex;
    align-items: baseline;
    padding-bottom: 25px;
    padding-left: 5px
}

@media (max-width: 767px) {
    .tag-products__page-description {
        margin-top:4px;
        margin-bottom: 12px;
        padding: 0
    }
}

.tag-products__page-description-caption {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 0.6);
    font-size: calc(14px * var(--font-size-paragraph, 1));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis
}

.tag-products__page-description-caption--expanded {
    white-space: normal;
    overflow: visible;
    text-overflow: unset;
    word-wrap: break-word
}

.tag-products__page-description-show-more {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    display: none;
    font-size: calc(14px * var(--font-size-paragraph, 1));
    font-weight: 400;
    padding-left: 8px;
    text-decoration-line: underline;
    white-space: nowrap;
    cursor: pointer
}

.tag-products__suggestion-tags-title {
    padding: 7px 5px 0 5px;
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    font-size: calc(16px * var(--font-size-paragraph, 1))
}

.tag-products__suggestion-tags-container {
    margin-top: 12px;
    margin-bottom: 32px;
    padding: 0 5px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px
}

.tag-products__suggestion-tag {
    display: flex;
    padding: 8px 12px;
    font-size: calc(16px * var(--font-size-paragraph, 1));
    justify-content: center;
    align-items: center;
    border-radius: 2px;
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 80%, 0.2);
    word-break: break-word
}

.tag-products__suggestion-tag:hover,.tag-products__suggestion-tag:active {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 60%, 0.8)
}

@media (hover: none) {
    .tag-products__suggestion-tag:hover {
        color:hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
        background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 80%, 0.2)
    }

    .tag-products__suggestion-tag:active {
        color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
        background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 60%, 0.8)
    }
}

@media (max-width: 767px) {
    .tag-products__suggestion-tag {
        font-size:calc(14px * var(--font-size-paragraph, 1));
        padding: 4px 12px
    }
}

.tag-products__extended-recommendation-bar {
    height: 44px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    margin-top: 12px;
    margin-bottom: 20px;
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 80%, 0.2);
    border-radius: 4px
}

.tag-products__extended-recommendation-bar-content {
    display: flex;
    align-items: center;
    width: 100%
}

.tag-products__extended-recommendation-bar-title {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    font-size: calc(14px * var(--font-size-paragraph, 1));
    white-space: nowrap;
    margin-right: 8px
}

.tag-products__extended-recommendation-bar-recommendations {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 0.6);
    font-size: calc(14px * var(--font-size-paragraph, 1));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-right: 8px
}

.tag-products__bottom-sheet-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.5);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    z-index: 1000;
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.3s ease, visibility 0.3s ease
}

.tag-products__bottom-sheet-backdrop.active {
    visibility: visible;
    opacity: 1
}

.tag-products__bottom-sheet-backdrop.active .tag-products__bottom-sheet-content {
    transform: translateY(0)
}

.tag-products__bottom-sheet-content {
    min-height: 48vh;
    max-height: 80vh;
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), var(--page-background-l, 100%), 1);
    width: 100%;
    border-radius: 8px 8px 0 0;
    box-shadow: 0 -4px 8px rgba(0,0,0,0.2);
    transform: translateY(100%);
    transition: transform 0.3s ease
}

.tag-products__bottom-sheet-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 60px;
    padding: 16px;
    border-bottom: 1px solid #EEF1F6
}

.tag-products__bottom-sheet-header-title {
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    font-size: calc(18px * var(--font-size-paragraph, 1))
}

.tag-products__bottom-sheet-header-close {
    padding: 10px 0 10px 10px;
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    font-size: calc(16px * var(--font-size-paragraph, 1))
}

.tag-products__bottom-sheet-suggestion-tags-container {
    max-height: calc(80vh - 60px);
    overflow: scroll;
    padding: 16px;
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    gap: 8px
}

.tag-products__product-list-info.ProductList-info {
    margin-bottom: 0
}

.tag-products__sticky-bar {
    position: fixed;
    width: 100%;
    height: 52px;
    padding: 12px 15px;
    display: flex;
    opacity: 0;
    justify-content: space-between;
    align-items: center;
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), var(--page-background-l, 100%), 1);
    z-index: 19;
    box-shadow: 0px 2px 4px 0px rgba(0,0,0,0.08)
}

.tag-products__sticky-bar-sort-title {
    font-size: calc(14px * var(--font-size-paragraph, 1));
    width: max-content
}

.tag-products__sticky-bar-sort-dropdown {
    width: max-content
}

.tag-products__sticky-bar #mobile-stickybar-sort-select::after {
    top: 10px
}

.tag-products__sticky-bar-recommendation-button {
    display: flex;
    padding: 4px 8px;
    justify-content: center;
    align-items: center;
    border-radius: 2px;
    font-size: calc(14px * var(--font-size-paragraph, 1));
    color: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    background-color: hsla(var(--page-background-h, 0deg), var(--page-background-s, 0%), 80%, 0.2);
    border: none
}

.tag-products__sticky-bar-recommendation-button-tag-icon {
    width: 16px;
    height: 16px;
    fill: hsla(var(--page-text-h, 0deg), var(--page-text-s, 0%), var(--page-text-l, 20%), 1);
    margin-right: 4px
}

.tag-products__sticky-bar-visible.header--sticky {
    box-shadow: none
}

檔案名稱:theme.liquid

  1. 請到 theme.liquidcategory.js 新增支援的頁面需包含屬性標籤頁: 搜尋程式碼 {{ 'category.js' | asset_source }} ,並在上方判斷位置新增指定程式碼 page.identifier == 'tag_products'










以下為範例結果(請勿直接複製此段落到你的檔案)

  {% if page.identifier != 'express_checkout' and page.identifier != 'checkout' %}
    {% if use_revamped_footer %}
      {% render_footer %}
    {% else %}
      {% include 'footer.liquid' %}
    {% endif %}
  {% endif %}

  {{ 'menu.js' | asset_source }}
  {{ 'general.js' | asset_source }}
  {{ 'cart_panel.js' | asset_source }}
  {% if page.identifier == 'product_list' or 
        page.identifier == 'category' or
        page.identifier == 'promotion_page' or
        page.identifier == 'redeem_gift' or
        page.identifier == 'tag_products' or              -> 請新增左邊程式碼即可    
        homepage == 'shop_all'
  %}
    {{ 'category.js' | asset_source }}
  {% endif %}
  {% if page.identifier == 'product_detail' %}
    {{ 'product_detail.js' | asset_source }}
  {% endif %}
  {% if page.url == '/blog/posts' %}
    {{ 'posts.js' | asset_source }}
  {% endif %}

*Note: category.js 控制分類頁原生的側邊欄 & 篩選器(如底下附圖)

客製化商品屬性標籤頁部分元件之行為 (Optional)

步驟 1資源檔新增 tag_products_page.js.liquid

步驟 2 新增下列程式碼至 tag_products_page.js.liquid

function initializeCaption(caption, showMore) {
    if (!caption || !showMore) return;

    function checkOverflow() {
        const isOverflowing = caption.scrollWidth > caption.clientWidth;
        showMore.style.display = isOverflowing ? "block" : "none"; 
    }

    function expandText() {
        caption.classList.add("tag-products__page-description-caption--expanded");
        showMore.style.display = "none"; 
        window.removeEventListener("resize", checkOverflow);
    }

    showMore.addEventListener("click", expandText);
    checkOverflow();
    window.addEventListener("resize", checkOverflow);
}

const desktopCaption = document.querySelector(".js-tag-products-desktop-description-caption");
const mobileCaption = document.querySelector(".js-tag-products-mobile-description-caption");
const desktopShowMore = document.querySelector(".js-tag-products-desktop-description-show-more");
const mobileShowMore = document.querySelector(".js-tag-products-mobile-description-show-more");

initializeCaption(desktopCaption, desktopShowMore);
initializeCaption(mobileCaption, mobileShowMore);

const CLICK_MOBILE_RECOMMENDATION_BAR_EVENT_TYPE = 'AttributeTagEntranceClick';
const bottomSheetBackdrop = document.querySelector('.tag-products__bottom-sheet-backdrop');
const bottomSheetContent = document.querySelector('.tag-products__bottom-sheet-content');
const recommendationBar = document.querySelector('.js-open-bottom-sheet');
const closeBtn = document.querySelector('.js-close-bottom-sheet');
const OPEN_BOTTOM_SHEET_EVENT_TYPE = {
    fixed: 'fixed',
    floating: 'floating'
};

function openBottomSheet(button_type) {
    bottomSheetBackdrop.classList.add('active');
    document.body.style.overflow = 'hidden';
    window.APP_EXTENSION_SDK_ANGULAR_JS_LOADED.then(() => {
        const injector = angular.element(document.querySelector('body'))?.injector();
        const trackerService = injector.get('trackerService');
        trackerService.track({
            type: CLICK_MOBILE_RECOMMENDATION_BAR_EVENT_TYPE,
            data: {
                button_type
            },
        })
    });
}

function closeBottomSheet() {
    bottomSheetBackdrop.classList.remove('active');
    document.body.style.overflow = '';
}

recommendationBar?.addEventListener('click', () => openBottomSheet(OPEN_BOTTOM_SHEET_EVENT_TYPE.fixed));
closeBtn?.addEventListener('click', closeBottomSheet);
bottomSheetBackdrop?.addEventListener('click', closeBottomSheet);
bottomSheetContent?.addEventListener('click', (event) => {
    event.stopPropagation();
});

const globalStickyHeaderTargets = ['#shopline-section-header', '.js-navbar-mobile', '#shopline-section-announcement'];

function getLastStickySection() {
    const $sections =[];
    globalStickyHeaderTargets.forEach(function(selector) {
      if (document.querySelector(selector)) {
        $sections.push(document.querySelector(selector));
      }
    });
    let style = {};
    for (let i = 0; i < $sections.length; i++) {
      if ($sections[i]) {
        style = window.getComputedStyle ? window.getComputedStyle($sections[i]) : $sections[i].style;
        if (style.position === 'sticky' && $sections[i].getBoundingClientRect().height > 0) {
          return $sections[i];
        }
      }
    }
  };

function getStickyBarTop() {
    let top = 0;
    let $lastStickySection = getLastStickySection();
    if ($lastStickySection) {
        const rect = $lastStickySection.getBoundingClientRect();
        const lastStickySectionHeight = Math.floor(rect.height);
        /* use getComputedStyle() instead of getBoundingClientRect() to get top, since top returned by getBoundingClientRect() is problematic in older version of safari mobile (https://bugs.webkit.org/show_bug.cgi?id=173841) */
        const lastStickySectionTop = parseFloat(window.getComputedStyle($lastStickySection).top);
        top += (lastStickySectionHeight + lastStickySectionTop);
    }
    /* Mobile browser has a slight space so we round the top value here to compensate */
    return top - 1;
}

const stickyBarElement = document.querySelector('.tag-products__sticky-bar');

function isVisibleInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
    )
}

let isScrollEventRegistered = false;
let showStickyBarTimer = null;
let hideStickyBarTimer = null;
let lastScrollTop = document.documentElement.scrollTop || document.body.scrollTop;

function toggleHeaderBoxShadow(action) {
  const headerElement = document.querySelector('.js-navbar-mobile');
  const hideHeaderBoxShadow = action === 'hide';
  if (headerElement) {
    headerElement.classList.toggle('tag-products__sticky-bar-visible', hideHeaderBoxShadow);
  }
};

const TOGGLE_STIKCY_BAR_TIMEOUT = 100;

function showStickyBar() {
  if (!showStickyBarTimer) {
    showStickyBarTimer = setTimeout(() => {
      requestAnimationFrame(() => {
        $(stickyBarElement).css({
          top: getStickyBarTop(),
          opacity: 1,
        });
        toggleHeaderBoxShadow('hide');
        showStickyBarTimer = null;
      });
    }, TOGGLE_STIKCY_BAR_TIMEOUT);
  }
}

function hideStickyBar() {
  if (!hideStickyBarTimer) {
    hideStickyBarTimer = setTimeout(() => {
      requestAnimationFrame(() => {
        $(stickyBarElement).css({
          opacity: 0,
        });
        toggleHeaderBoxShadow('show');
        hideStickyBarTimer = null;
      });
    }, TOGGLE_STIKCY_BAR_TIMEOUT);
  }
}


function clearTimers() {
  if (showStickyBarTimer) {
    clearTimeout(showStickyBarTimer);
    showStickyBarTimer = null;
  }
  if (hideStickyBarTimer) {
    clearTimeout(hideStickyBarTimer);
    hideStickyBarTimer = null;
  }
};

const SCROLL_THRESHOLD = 15;

function checkShouldShowStickyBar() {
  const currentScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const isScrollPositionUnchanged = currentScrollTop === lastScrollTop;
  /* Ignore tiny movements (like those when tapping to stop scrolling) */
  const isScrollChangeTrivial = Math.abs(currentScrollTop - lastScrollTop) < SCROLL_THRESHOLD;

  if (isScrollPositionUnchanged || isScrollChangeTrivial) {
    lastScrollTop = currentScrollTop;
    return;
  }

  const isStickyBarHidden = stickyBarElement.style.opacity === '0';
  const isScrollingUp = currentScrollTop < lastScrollTop;
  const isRecommendationBarVisible = isVisibleInViewport(recommendationBar);

  if (isScrollingUp && !isRecommendationBarVisible) {
    if (hideStickyBarTimer) {
      clearTimeout(hideStickyBarTimer);
      hideStickyBarTimer = null;
    }
    if (isStickyBarHidden) showStickyBar();
  } else {
    if (showStickyBarTimer) {
      clearTimeout(showStickyBarTimer);
      showStickyBarTimer = null;
    }
    if (!isStickyBarHidden) hideStickyBar();
  };

  lastScrollTop = currentScrollTop;
};

const CHECK_SCROLL_THROTTLE_TIMEOUT = 100;
const throttledCheckShouldShowStickyBar = _.throttle(checkShouldShowStickyBar, CHECK_SCROLL_THROTTLE_TIMEOUT);

const footerObserver = new IntersectionObserver((entries) => {
  const footerEntry = entries[0];

  if (footerEntry.isIntersecting) {
    if (isScrollEventRegistered) {
      clearTimers();
      hideStickyBar();
      window.removeEventListener("scroll", throttledCheckShouldShowStickyBar);
      isScrollEventRegistered = false;
    }
  } else {
    if (!isScrollEventRegistered) {
      window.addEventListener("scroll", throttledCheckShouldShowStickyBar);
      isScrollEventRegistered = true;
    }
  }
}, {
  root: null,
  threshold: 0.3
});

if (window.matchMedia('(max-width: 768px)').matches) {
  window.addEventListener("scroll", throttledCheckShouldShowStickyBar);
  const stickyBarRecommendationButton = document.querySelector('.js-sticky-bar-recommendation');
  stickyBarRecommendationButton?.addEventListener('click', () => openBottomSheet(OPEN_BOTTOM_SHEET_EVENT_TYPE.floating));
  const footerElement = document.querySelector("#Footer");
  if (footerElement) {
    footerObserver.observe(footerElement);
  }
}

步驟 3 客製化商品屬性標籤頁指定元件

  1. 手機版本商品屬性標籤頁 sticky bar 的顯示邏輯

手機版本的商品屬性標籤頁,在使用者將頁面向上滑動時會顯示 sticky bar,若先前有客製化過頁首,可能會導致顯示的位置有偏誤,可以透過以下的區塊調整 sticky bar 顯示的邏輯

區塊名稱作用 / 影響範圍
const globalStickyHeaderTargets頁首區塊的 id
function getLastStickySection負責取得頁首最下面有 sticky 屬性的 element
function getStickyBarTop負責計算 sticky bar 的 top 屬性
  1. mobile 相關標籤彈窗的顯示以及收合

區塊名稱作用 / 影響範圍
function openBottomSheet管理開啟 mobile 相關標籤彈窗的行為
function closeBottomSheet管理關閉 mobile 相關標籤彈窗的行為
  1. 商品標籤頁敘述文字長度的處理

區塊名稱作用 / 影響範圍
function initializeCaption標籤頁敘述文字長度處裡 (過長則顯示…)

注意事項

若曾經更改過 navigation bar 的樣式可能會導致手機版本的屬性標籤頁跑版,至 theme.css.liquid 請至檔案最底下新增下列樣式來維持手機版本的商品屬性標籤頁顯示

.tag-products__sticky-bar {
    display: none;
}