需求背景

网站的搜索框是用户和网站交互的一种途径。用户使用搜索框的时候往往是带有目的性的。比如想买春天的裙子,比如想买带花纹的上衣。这种带有明确目的的流量是十分珍贵的。如果加以正确的引流,往往可能会带来转化。除此之外,这里还是站长对网站进行营销的另外一个入口。比如现在网站想强烈推荐某个风格或者季节的衣服,那么直接可以在这里开入口,将站内的引流灵活运用。至于行业热搜词等,因站而异。可以参考竞品网站或者从google trending里面去找。

现存的原生的shopify搜索框比较素,功能也比较单一如何能够将上面的想法融入到我们的shopify网站是当下应该考虑的问题。

1. 代码更新

由于不同主题的搜索框实现原理都不一样,重口难调。所以博主这边只对原生的Dawn主题进行优化,大家自行去进行改版即可。原理就是,在加载搜索数据的地方给它塞进去我们设定好的关键热搜词。

predictive-search.js

  1. class PredictiveSearch extends HTMLElement {
  2. constructor() {
  3. super();
  4. this.cachedResults = {};
  5. this.input = this.querySelector('input[type="search"]');
  6. this.predictiveSearchResults = this.querySelector('[data-predictive-search]');
  7. this.isOpen = false;
  8. this.setupEventListeners();
  9. }
  10. setupEventListeners() {
  11. const form = this.querySelector('form.search');
  12. form.addEventListener('submit', this.onFormSubmit.bind(this));
  13. this.input.addEventListener('input', debounce((event) => {
  14. this.onChange(event);
  15. }, 300).bind(this));
  16. this.input.addEventListener('focus', this.onFocus.bind(this));
  17. this.addEventListener('focusout', this.onFocusOut.bind(this));
  18. this.addEventListener('keyup', this.onKeyup.bind(this));
  19. this.addEventListener('keydown', this.onKeydown.bind(this));
  20. }
  21. getQuery() {
  22. return this.input.value.trim();
  23. }
  24. onChange() {
  25. const searchTerm = this.getQuery();
  26. if (!searchTerm.length) {
  27. this.close(true);
  28. return;
  29. }
  30. this.getSearchResults(searchTerm);
  31. }
  32. onFormSubmit(event) {
  33. if (!this.getQuery().length || this.querySelector('[aria-selected="true"] a')) event.preventDefault();
  34. }
  35. onFocus() {
  36. const searchTerm = this.getQuery();
  37. this.open();
  38. if (!searchTerm.length) return;
  39. if (this.getAttribute('results') === 'true') {
  40. this.open();
  41. } else {
  42. this.getSearchResults(searchTerm);
  43. }
  44. }
  45. onFocusOut() {
  46. setTimeout(() => {
  47. if (!this.contains(document.activeElement)) this.close();
  48. })
  49. }
  50. onKeyup(event) {
  51. if (!this.getQuery().length) this.close(true);
  52. event.preventDefault();
  53. switch (event.code) {
  54. case 'ArrowUp':
  55. this.switchOption('up')
  56. break;
  57. case 'ArrowDown':
  58. this.switchOption('down');
  59. break;
  60. case 'Enter':
  61. this.selectOption();
  62. break;
  63. }
  64. }
  65. onKeydown(event) {
  66. // Prevent the cursor from moving in the input when using the up and down arrow keys
  67. if (
  68. event.code === 'ArrowUp' ||
  69. event.code === 'ArrowDown'
  70. ) {
  71. event.preventDefault();
  72. }
  73. }
  74. switchOption(direction) {
  75. if (!this.getAttribute('open')) return;
  76. const moveUp = direction === 'up';
  77. const selectedElement = this.querySelector('[aria-selected="true"]');
  78. const allElements = this.querySelectorAll('li');
  79. let activeElement = this.querySelector('li');
  80. if (moveUp && !selectedElement) return;
  81. this.statusElement.textContent = '';
  82. if (!moveUp && selectedElement) {
  83. activeElement = selectedElement.nextElementSibling || allElements[0];
  84. } else if (moveUp) {
  85. activeElement = selectedElement.previousElementSibling || allElements[allElements.length - 1];
  86. }
  87. if (activeElement === selectedElement) return;
  88. activeElement.setAttribute('aria-selected', true);
  89. if (selectedElement) selectedElement.setAttribute('aria-selected', false);
  90. this.setLiveRegionText(activeElement.textContent);
  91. this.input.setAttribute('aria-activedescendant', activeElement.id);
  92. }
  93. selectOption() {
  94. const selectedProduct = this.querySelector('[aria-selected="true"] a, [aria-selected="true"] button');
  95. if (selectedProduct) selectedProduct.click();
  96. }
  97. getSearchResults(searchTerm) {
  98. const queryKey = searchTerm.replace(" ", "-").toLowerCase();
  99. this.setLiveRegionLoadingState();
  100. if (this.cachedResults[queryKey]) {
  101. this.renderSearchResults(this.cachedResults[queryKey]);
  102. return;
  103. }
  104. fetch(`${routes.predictive_search_url}?q=${encodeURIComponent(searchTerm)}&${encodeURIComponent('resources[type]')}=product&${encodeURIComponent('resources[limit]')}=4&section_id=predictive-search`)
  105. .then((response) => {
  106. if (!response.ok) {
  107. var error = new Error(response.status);
  108. this.close();
  109. throw error;
  110. }
  111. return response.text();
  112. })
  113. .then((text) => {
  114. const resultsMarkup = new DOMParser().parseFromString(text, 'text/html').querySelector('#shopify-section-predictive-search').innerHTML;
  115. this.cachedResults[queryKey] = resultsMarkup;
  116. this.renderSearchResults(resultsMarkup);
  117. })
  118. .catch((error) => {
  119. this.close();
  120. throw error;
  121. });
  122. }
  123. setLiveRegionLoadingState() {
  124. this.statusElement = this.statusElement || this.querySelector('.predictive-search-status');
  125. this.loadingText = this.loadingText || this.getAttribute('data-loading-text');
  126. this.setLiveRegionText(this.loadingText);
  127. this.setAttribute('loading', true);
  128. }
  129. setLiveRegionText(statusText) {
  130. this.statusElement.setAttribute('aria-hidden', 'false');
  131. this.statusElement.textContent = statusText;
  132. setTimeout(() => {
  133. this.statusElement.setAttribute('aria-hidden', 'true');
  134. }, 1000);
  135. }
  136. renderSearchResults(resultsMarkup) {
  137. this.predictiveSearchResults.querySelector('.predictive-search__keywords').classList.add('hide');
  138. this.predictiveSearchResults.querySelector('.predictive-search__results').innerHTML = resultsMarkup;
  139. this.setAttribute('results', true);
  140. this.setLiveRegionResults();
  141. this.open();
  142. }
  143. setLiveRegionResults() {
  144. this.removeAttribute('loading');
  145. this.setLiveRegionText(this.querySelector('[data-predictive-search-live-region-count-value]').textContent);
  146. }
  147. getResultsMaxHeight() {
  148. this.resultsMaxHeight = window.innerHeight - document.getElementById('shopify-section-header').getBoundingClientRect().bottom;
  149. return this.resultsMaxHeight;
  150. }
  151. open() {
  152. this.predictiveSearchResults.style.maxHeight = this.resultsMaxHeight || `${this.getResultsMaxHeight()}px`;
  153. this.setAttribute('open', true);
  154. this.input.setAttribute('aria-expanded', true);
  155. this.isOpen = true;
  156. }
  157. close(clearSearchTerm = false) {
  158. if (clearSearchTerm) {
  159. this.input.value = '';
  160. this.removeAttribute('results');
  161. }
  162. const selected = this.querySelector('[aria-selected="true"]');
  163. if (selected) selected.setAttribute('aria-selected', false);
  164. this.input.setAttribute('aria-activedescendant', '');
  165. this.removeAttribute('open');
  166. this.input.setAttribute('aria-expanded', false);
  167. this.resultsMaxHeight = false
  168. this.predictiveSearchResults.removeAttribute('style');
  169. this.isOpen = false;
  170. }
  171. }
  172. customElements.define('predictive-search', PredictiveSearch);

header.liquid

  1. 在style里面添加样式

    1. ul.keywords_list {
    2. list-style: none;
    3. display: inline-table;
    4. padding: 0 20px;
    5. width: 100%;
    6. }
    7. ul.keywords_list a {
    8. text-decoration: none;
    9. }
    10. ul.keywords_list .keywords_item {
    11. float: left;
    12. width: 50%;
    13. margin: 5px 0;
    14. }
  2. 搜索 predictive-search predictive-search—header 将div标签内的内容更新为

    1. <div class="predictive-search predictive-search--header" tabindex="-1" data-predictive-search>
    2. <div class="predictive-search__keywords">
    3. <h2 class="predictive-search__heading text-body caption-with-letter-spacing">
    4. HOT SEARCH
    5. <svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
    6. <circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
    7. </svg>
    8. </h2>
    9. <ul class="keywords_list">
    10. {%- for block in section.blocks -%}
    11. <li class="keywords_item">
    12. <a href="{{ block.settings.collection.url }}">
    13. <span style="color: {{ block.settings.keyword_color }};">{{ block.settings.keyword }}</span>
    14. </a>
    15. </li>
    16. {%- endfor -%}
    17. </ul>
    18. </div>
    19. <div class="predictive-search__results">
    20. <div class="predictive-search__loading-state">
    21. <svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
    22. <circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
    23. </svg>
    24. </div>
    25. </div>
    26. </div>
  3. 在schema中添加block配置

    1. "blocks": [
    2. {
    3. "type": "keyword",
    4. "name": "关键词配置",
    5. "settings": [
    6. {
    7. "type": "header",
    8. "content": "关键词配置项"
    9. },
    10. {
    11. "type": "collection",
    12. "id": "collection",
    13. "label": "关键词collection链接"
    14. },
    15. {
    16. "type": "text",
    17. "id": "keyword",
    18. "label": "显示的关键词"
    19. },
    20. {
    21. "type": "color",
    22. "id": "keyword_color",
    23. "label": "关键词颜色"
    24. }
    25. ]
    26. }
    27. ]

    3. 效果展示

    店铺地址: https://asen-practice.myshopify.com/ 密码:test

点击查看【bilibili】

Tips: 如有引用请标注源文章地址

关注我的【小红书】,第一时间掌握更新动态


你的鼓励就是我创作的动力!
zanshang.png