开发了个能够在不同搜索引擎里丝滑切换的浏览器插件

369 阅读3分钟

前言

在我们在搜索时,可能会根据自己的需要,选择不同的搜索引擎。笔者这里就为之前开发的新标签页插件添加一个能够在google、baidu、bing等不同搜索引擎的搜索结果页自由切换的功能,帮助我们在多个搜索引擎之间快速切换,无需重复输入关键词。具体效果如下:

quickChangeSe.png

下面我介绍下这个功能的简单实现。

如何识别不同搜索引擎的关键词

观察不同的搜索引擎,我们能整理出下面这样的映射:

/**
 * 搜索引擎映射
 * @typedef {Object} SearchEngine
 * @property {string} name - 搜索引擎的名称
 * @property {string} url - 搜索引擎的查询 URL 模板
 * @property {string} query - 查询参数的键名
 * @property {string} icon - 搜索引擎的图标 URL
 */

/**
 * @type {Object.<string, SearchEngine>}
 */
const SE = {
  baidu: {
    name: "百度",
    url: "https://www.baidu.com/s?wd=",
    query: "wd",
    icon: "https://www.baidu.com/favicon.ico"
  },
  google: {
    name: "谷歌",
    url: "https://www.google.com.hk/search?q=",
    query: "q",
    icon: "https://www.google.com.hk/favicon.ico"
  },
  bing: {
    name: "必应",
    url: "https://cn.bing.com/search?q=",
    query: "q",
    icon: "https://cn.bing.com/sa/simg/bing_p_rr_teal_min.ico"
  },
  sougou: {
    name: "搜狗",
    url: "https://www.sogou.com/web?query=",
    query: "query",
    icon: "https://sogou.com/images/logo/new/favicon.ico?nv=1&v=3"
  },
  360: {
    name: "360",
    url: "https://www.so.com/s?q=",
    query: "q",
    icon: "https://www.so.com/favicon.ico"
  }
}

其中url为不同搜索引擎的路由,query为不同搜索引擎获取关键词的query参数。得到这样一份映射以后,我们可以整理出下面这样的思路: 获取当前页面location信息,判断当前属于哪种搜索引擎,从而拿到对应的关键词,将关键词拼接到映射中对应的链接,即对应着关键词在某个搜索引擎下的链接。

如何实现UI界面让用户在不同搜索引擎结果页之间跳转

这里需要用到浏览器插件中的content_scripts功能,至于content_scripts功能的介绍,我在之前的文章写过(上文),这里不做赘述,这里直接上代码。

let curSE = getCurSE() // 存储当前搜索引擎
let keyword = "" // 存储当前搜索关键字

/**
 * 搜索引擎映射
 * @typedef {Object} SearchEngine
 * @property {string} name - 搜索引擎的名称
 * @property {string} url - 搜索引擎的查询 URL 模板
 * @property {string} query - 查询参数的键名
 * @property {string} icon - 搜索引擎的图标 URL
 */

/**
 * @type {Object.<string, SearchEngine>}
 */
const SE = {
  baidu: {
    name: "百度",
    url: "https://www.baidu.com/s?wd=",
    query: "wd",
    icon: "https://www.baidu.com/favicon.ico"
  },
  google: {
    name: "谷歌",
    url: "https://www.google.com.hk/search?q=",
    query: "q",
    icon: "https://www.google.com.hk/favicon.ico"
  },
  bing: {
    name: "必应",
    url: "https://cn.bing.com/search?q=",
    query: "q",
    icon: "https://cn.bing.com/sa/simg/bing_p_rr_teal_min.ico"
  },
  sougou: {
    name: "搜狗",
    url: "https://www.sogou.com/web?query=",
    query: "query",
    icon: "https://sogou.com/images/logo/new/favicon.ico?nv=1&v=3"
  },
  360: {
    name: "360",
    url: "https://www.so.com/s?q=",
    query: "q",
    icon: "https://www.so.com/favicon.ico"
  }
}

// 获取当前搜索引擎
function getCurSE() {
  let curSE = ""
  const host = window.location.host
  if (host === "www.baidu.com") {
    curSE = "baidu"
  } else if (host.includes("google")) {
    curSE = "google"
  } else if (host.includes("bing.com")) {
    curSE = "bing"
  } else if (host.includes"sogou") {
    curSE = "sougou"
  } else if (host.includes("www.so.com")) {
    curSE = "360"
  }
  return curSE
}
// 获取搜索关键字
function getSearchKeyword(curSE) {
  const search = window.location.search
  const query = new URLSearchParams(search)
  return query.get(SE[curSE].query)
}
// 生成搜索引擎a标签
function createSEItem(name, url, key) {
  const item = document.createElement("div")
  item.classList.add("ovo-search-engine-nav-item")
  item.innerHTML = `<img src="${SE[key].icon}" alt="${name}" >`

  // a标签
  const a = document.createElement("a")
  a.href = url
  a.innerHTML = `${name}`
  a.target = "_self"
  a.setAttribute("data-key", key)

  item.append(a)

  return item
}
// 获取搜索引擎导航
function generateSearchEngineNav(curSE) {
  const searchEngineNav = document.createElement("div")
  searchEngineNav.classList.add("ovo-search-engine-nav")
  keyword = getSearchKeyword(curSE)
  if (!keyword) return
  const navs = Object.keys(SE).map((key) => {
    const se = SE[key]
    return createSEItem(se.name, se.url + keyword, key)
  })
  searchEngineNav.append(...navs)
  document.body.appendChild(searchEngineNav)
}

// 更新搜索引擎导航
function updateSearchEngineNav(curSE) {
  // 找到原本的侧边栏
  const searchEngineNav = document.querySelector(".ovo-search-engine-nav")
  keyword = getSearchKeyword(curSE)
  if (!keyword) return
  if (searchEngineNav) { // 更新keyword
    const navs = searchEngineNav.querySelectorAll(".ovo-search-engine-nav-item")
    navs.forEach((nav) => {
      const aDom = nav.querySelector("a")
      const key = aDom.getAttribute("data-key")
      const url = SE[key].url + keyword
      aDom.href = url
    })
  } else { // 如果侧边栏丢失了 需重新生成
    generateSearchEngineNav(curSE)
  }
}

const debounceUpdateSearchEngineNav = debounce(updateSearchEngineNav, 1000)

// 监听 DOM 变化回调
function seMutationObserverCallback() {
  requestAnimationFrame(() => {
    // 获取当前的搜索引擎
    curSE = getCurSE()
    // 更新侧边栏
    curSE && debounceUpdateSearchEngineNav(curSE)
  })
}

// 监听 DOM 变化
function setupChangeSEMutationObserver() {
  const observer = new MutationObserver(seMutationObserverCallback)
  const config = { childList: true, subtree: true } // 监听子节点变化 和 所有后代节点变化
  observer.observe(document.body, config)
}

// 防抖
function debounce(fn, delay) {
  let timer = null
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, delay)
  }
}
// 判断是否启用功能
window.chrome.storage.sync.get(["enable"], (result) => {
  if (result.enable) {
    // 生成切换搜索引擎的侧边栏
    generateSearchEngineNav(curSE)
    // 启动监听 保证在翻页、重新搜索过程中搜索词正确
    setupChangeSEMutationObserver()
  }
})

至此,大功告成,具体效果如下图所示:

changeSe.gif

结语

最后,本文主要为技术分享,仅供学习,欢迎大家体验笔者独立开发的新标签页插件~