web_highlight

选中高亮!持续更新中。。

当前为 2024-02-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name web_highlight
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.3
  5. // @description 选中高亮!持续更新中。。
  6. // @author You
  7. // @license MIT
  8. // @match *://*/*
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Your code here...
  17.  
  18. // 在网页中注入自定义的 CSS 样式
  19. GM_addStyle(`
  20. .menu_dialog {
  21. width: 30px;
  22. height: 30px;
  23. border-radius: 5px;
  24. position: absolute;
  25. background-color: red;
  26. cursor: pointer;
  27. color: white;
  28. }
  29. .pale_card {
  30. position: absolute;
  31. background-color: white;
  32. border: 1px solid gray;
  33. padding: 5px;
  34. }
  35. .pale_highlight_color {
  36. width: 10px;
  37. height: 10px;
  38. border-radius: 2px;
  39. cursor: pointer;
  40. margin-left: 3px;
  41. transition: width 0.3s, height 0.3s;
  42. }
  43. .pale_highlight_color:first-child {
  44. margin-left: 0;
  45. }
  46. .pale_highlight_color:hover {
  47. border: 2px solid;
  48. }
  49. `);
  50.  
  51. const sites = {}
  52. const href = window.location.href;
  53. sites[href] = []
  54. let target = null // 选中的关键词的目标元素
  55. let menuDom = null // 菜单图标元素
  56. const disabledTagNameList = ['INPUT', 'TEXTAREA', 'IMG'] // 禁用的标签
  57.  
  58. function findClosestChildWithKeyword(startingElement, keyword) {
  59. // 找到包含keyword的元素
  60. const queue = [startingElement];
  61. while (queue.length > 0) {
  62. const currentElement = queue.shift();
  63. if (currentElement.textContent.includes(keyword)) {
  64. return currentElement;
  65. }
  66. if (currentElement.children) {
  67. queue.push(...currentElement.children);
  68. }
  69. }
  70. return null; // 如果找不到含有关键词的子元素,则返回 null
  71. }
  72.  
  73. function keyHighlight(key, color, keyTarget) {
  74. if (keyTarget) {
  75. // 根据元素查关键词
  76. key = keyTarget.innerText
  77. if (!sites[href].includes(key)) {
  78. sites[href].push(key)
  79. }
  80. } else {
  81. // 根据关键词找到元素
  82. keyTarget = findClosestChildWithKeyword(key)
  83. }
  84. // 设置背景
  85. const keyBackgroundSpan = document.createElement('span')
  86. keyBackgroundSpan.style.background = color
  87. keyBackgroundSpan.innerText = key
  88. keyTarget.innerHTML = ''
  89. keyTarget.appendChild(keyBackgroundSpan)
  90. }
  91.  
  92. class ColorPale {
  93. // 参数
  94. colorList = ['#faa8a8', 'blue', 'green', 'yellow', 'orange']
  95. cardWidth = 20
  96. iconWidth = 30
  97. palePadding = 5
  98. colorMargin = 3
  99.  
  100. // 以下参数仅供程序使用
  101. paleWidth = 0
  102. paleHeight = 0
  103. paleLeft = 0
  104. paleTop = 0
  105. pale = null
  106. isOnCard = false // 鼠标是否进入色卡
  107.  
  108. initColorPale(menuDom) {
  109. this.paleWidth = this.colorList.length * this.cardWidth + this.palePadding * 2 + (this.colorList.length - 1) * this.colorMargin
  110. this.paleHeight = this.cardWidth + this.palePadding * 2
  111. this.paleLeft = parseInt(menuDom.style.left) - ((this.paleWidth - this.iconWidth) / 2)
  112. this.paleTop = parseInt(menuDom.style.top) + this.iconWidth
  113.  
  114. this.pale = document.createElement('div')
  115. this.pale.className = 'pale_card'
  116. this.pale.style.width = this.paleWidth + 'px'
  117. this.pale.style.height = this.paleHeight + 'px'
  118. this.pale.style.left = this.paleLeft + 'px'
  119. this.pale.style.top = this.paleTop + 'px'
  120. this.pale.style.zIndex = '9999';
  121. this.pale.style.display = 'none' // 隐藏
  122.  
  123. this.colorList.forEach(color => {
  124. const colorCard = document.createElement('div')
  125. colorCard.className = 'pale_highlight_color'
  126. colorCard.style.width = this.cardWidth + 'px'
  127. colorCard.style.height = this.cardWidth + 'px'
  128. colorCard.style.backgroundColor = color
  129. colorCard.style.zIndex = '9998';
  130. colorCard.addEventListener('click', (e) => {
  131. keyHighlight(null, color, target)
  132. e.stopPropagation();
  133. })
  134. colorCard.addEventListener('mouseup', (e) => {
  135. // 阻止在设置色卡时触发清除函数
  136. e.stopPropagation();
  137. })
  138. this.pale.appendChild(colorCard)
  139. })
  140.  
  141. this.pale.addEventListener('mouseenter', (e) => {
  142. this.isOnCard = true
  143. })
  144. this.pale.addEventListener('mouseleave', (e) => {
  145. this.isOnCard = false
  146. this.closePale()
  147. })
  148.  
  149. document.body.appendChild(this.pale)
  150. return Promise.resolve(this)
  151. }
  152.  
  153. showPale(menuDom) {
  154. if (this.pale) {
  155. if (menuDom) {
  156. this.paleLeft = parseInt(menuDom.style.left) - ((this.paleWidth - this.iconWidth) / 2)
  157. this.paleTop = parseInt(menuDom.style.top) + this.iconWidth
  158. }
  159. this.pale.style.left = this.paleLeft + 'px'
  160. this.pale.style.top = this.paleTop + 'px'
  161. this.pale.style.display = 'flex'
  162. return Promise.resolve(this)
  163. } else if (menuDom) {
  164. return this.initColorPale(menuDom).then(Pale => {
  165. Pale.pale.style.display = 'flex'
  166. return Pale
  167. })
  168. }
  169. }
  170.  
  171. closePale() {
  172. // 鼠标在色卡上不能关闭
  173. if (!this.isOnCard) {
  174. this.pale.style.display = 'none'
  175. }
  176. }
  177. }
  178.  
  179. // 初始化
  180. const colorPale = new ColorPale
  181.  
  182. // 滚动后重新加载高亮关键词
  183. window.addEventListener('scroll', highlight);
  184.  
  185. let selectionTimeout;
  186.  
  187. document.addEventListener('selectionchange', function() {
  188. // 移除之前添加的mouseup监听器
  189. document.removeEventListener('mouseup', handleMouseUp);
  190.  
  191. // 添加新的mouseup监听器
  192. document.addEventListener('mouseup', handleMouseUp);
  193. });
  194.  
  195. /**document.addEventListener('click', () => {
  196. // 监听选中消失的事件
  197. const selection = window.getSelection();
  198. if (selection.toString().length > 0) {
  199. // 没有选中
  200. clearData()
  201. }
  202. })*/
  203.  
  204. function handleMouseUp(event) {
  205. // 删除之前的数据
  206. clearData()
  207. const selection = window.getSelection();
  208. if (selection.toString().length > 0 && !disabledTagNameList.includes(event.target.tagName)) {
  209. // 用户选中了文本,可以在这里处理相应的逻辑
  210. target = event.target;
  211. console.dir(target, 'target');
  212. // 不处理特殊标签 input/textarea
  213. menuDom = document.createElement('div');
  214. menuDom.className = 'menu_dialog';
  215. menuDom.style.top = (event.clientY + 10) + 'px'
  216. menuDom.style.left = (event.clientX + 10) + 'px'
  217. menuDom.style.zIndex = '9999';
  218. menuDom.innerHTML = '色卡';
  219.  
  220. menuDom.addEventListener('mouseup', (e) => {
  221. // 阻止在设置色卡时触发清除函数
  222. e.stopPropagation();
  223. })
  224.  
  225. // colorPale.showPale(menuDom).then(pale => {
  226. menuDom.addEventListener('mouseenter', (e) => {
  227. console.log(e, '数据')
  228. colorPale.showPale(menuDom).then(pale => {
  229. // 鼠标移出时删除菜单
  230. const handleMouseLeave = () => {
  231. setTimeout(() => {
  232. pale.closePale();
  233. menuDom.removeEventListener('mouseleave', handleMouseLeave);
  234. }, 300)
  235. };
  236. menuDom.addEventListener('mouseleave', handleMouseLeave)
  237. })
  238. })
  239. document.body.appendChild(menuDom);
  240. // })
  241. }
  242. }
  243.  
  244. function clearData() {
  245. target = null
  246. if (menuDom) {
  247. document.body.removeChild(menuDom)
  248. menuDom = null
  249. }
  250. }
  251.  
  252. function highlight() {
  253. sites[href].forEach(keyword => {
  254. const { word, color } = keyword
  255.  
  256. })
  257. }
  258.  
  259. function getSelectionElement() {
  260. const selection = window.getSelection();
  261. if (selection.rangeCount > 0) {
  262. const range = selection.getRangeAt(0);
  263. const startContainer = range.startContainer;
  264. const endContainer = range.endContainer;
  265.  
  266. // 找到最接近的包含选中文本的父元素
  267. const closestParentElement = (startContainer.nodeType === 3) ? startContainer.parentNode : startContainer;
  268. // closestParentElement 就是包含选中文本的最接近的父元素
  269. return closestParentElement;
  270. }
  271. }
  272. })();