web_highlight

选中高亮!

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

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