去年我剛到公司的時候,旁邊同事問我wangeditor怎么實現格式刷功能,我找了很多資料勉勉強強實現了一個,一直想寫個文章記錄下,拖到現在才寫,真是老了呢。
新建一個js文件,暫且命名為formatBrush.js,直接上代碼:
1 // 參考資料 2 // https://www.jianshu.com/p/13dca2711b6e 3 // https://juejin.cn/post/6844903737161433102 4 5 import wangEditor from "wangeditor"; 6 7 const { BtnMenu } = wangEditor; 8 // 第一,菜單 class ,Button 菜單繼承 BtnMenu class 9 class FormatBrushMenu extends BtnMenu { 10 constructor(editor) { 11 // data-title屬性表示當鼠標懸停在該按鈕上時提示該按鈕的功能簡述 12 // 圖表直接用的element-ui的圖標,請自行調整 13 const $elem = wangEditor.$( 14 `<div class="w-e-menu el-icon-s-open" data-title="格式刷"> 15 </div>` 16 ); 17 super($elem, editor); 18 const me = this; 19 me.editor = editor; 20 // 監聽編輯器鼠標釋放事件 21 editor.$textElem.on("mouseup", () => { 22 // 如果格式刷功能出於激活狀態 23 if (me._active) { 24 // 延遲執行,避免獲取不到正確的元素 25 setTimeout(() => { 26 // 復制格式刷樣式 27 pasteStyle(editor); 28 // 取消格式刷激活樣式 29 me.unActive(); 30 }, 100); 31 } 32 }); 33 } 34 // 菜單點擊事件 35 clickHandler() { 36 const me = this; 37 const editor = me.editor; 38 if (me._active) { 39 // 已經在激活狀態時取消激活 40 me.unActive(); 41 // 清空格式刷樣式數據 42 editor.copyStyleList = [] 43 } else { 44 // 沒有選中則終端 45 if (editor.selection.isSelectionEmpty()) return; 46 // 激活按鈕 47 me.active(); 48 // 獲取格式刷樣式 49 const domToParse = 50 editor.selection.getSelectionContainerElem().elems[0]; 51 const copyStyleList = parseDom(domToParse); 52 // 保存格式刷樣式 53 editor.copyStyleList = copyStyleList; 54 } 55 } 56 tryChangeActive() { } 57 } 58 59 // 菜單 key ,各個菜單不能重復 60 const menuKey = "formatBrush"; 61 62 // 注冊菜單 63 wangEditor.registerMenu(menuKey, FormatBrushMenu); 64 65 // 復制選中dom的樣式 66 function parseDom(dom) { 67 let targetDom = null; 68 const nodeArray = []; 69 70 getTargetDom(dom); 71 72 getAllStyle(targetDom); 73 74 function getTargetDom(dom) { 75 for (const i of dom.childNodes) { 76 if (i.nodeType === 3 && i.nodeValue && i.nodeValue.trim() !== "") { 77 targetDom = dom; 78 return; 79 } 80 } 81 getTargetDom(dom.children[0]); 82 } 83 84 function getAllStyle(dom) { 85 if (!dom) return; 86 const tagName = dom.tagName.toLowerCase(); 87 if (tagName === "p") { 88 nodeArray.push({ 89 tagName: "span", 90 attributes: Array.from(dom.attributes).map((i) => { 91 return { 92 name: i.name, 93 value: i.value 94 }; 95 }) 96 }); 97 return; 98 } else { 99 nodeArray.push({ 100 tagName: tagName, 101 attributes: Array.from(dom.attributes).map((i) => { 102 return { 103 name: i.name, 104 value: i.value 105 }; 106 }) 107 }); 108 getAllStyle(dom.parentNode); 109 } 110 } 111 return nodeArray; 112 } 113 114 function addStyle(text, nodeArray) { 115 let currentNode = null; 116 nodeArray.forEach((ele, index) => { 117 const node = document.createElement(ele.tagName); 118 for (const attr of ele.attributes) { 119 node.setAttribute(attr.name, attr.value); 120 } 121 if (index === 0) { 122 node.innerText = text; 123 currentNode = node; 124 } else { 125 node.appendChild(currentNode); 126 currentNode = node; 127 } 128 }); 129 return currentNode; 130 } 131 132 // 粘貼 133 function pasteStyle(editor) { 134 // 獲取格式刷保存的樣式 135 const copyStyleList = editor.copyStyleList; 136 // 有樣式說明格式刷被激活 137 if (copyStyleList) { 138 // 獲取當前選中內容 139 // 如果沒選中也會執行,再次使用需要重新激活格式刷功能 140 const text = editor.selection.getSelectionText(); 141 const targetDom = addStyle(text, copyStyleList); 142 editor.cmd.do("insertHTML", targetDom.outerHTML); 143 // 清空格式刷樣式 144 editor.copyStyleList = null; 145 } 146 }
vue組件代碼如下,基於ts(沒用ts的自己改改吧,話說vue3這種寫法就不行了呀,組合式api還是好用):
1 <template> 2 <div class="editor-el"> 3 <div></div> 4 <el-input v-show="false" v-model="value"></el-input> 5 </div> 6 </template> 7 8 <script lang="ts"> 9 // 引入 wangEditor 10 import wangEditor from "wangeditor"; 11 import { Component, Vue, Model, Emit, Watch } from "vue-property-decorator"; 12 import "./formatBrush"; 13 @Component({}) 14 export default class servicesNoticeEdit extends Vue { 15 // v-model綁定值 16 @Model("valuechange", { type: String }) value!: String; 17 @Emit("valuechange") setValue() {} 18 19 @Watch("value", { immediate: true }) updateValue(v: string) { 20 if (v) { 21 const me = this as any; 22 if (me.isUp) { 23 // 這里通過標識符避免重復觸發事件 24 me.isUp = false; 25 } else { 26 me.$nextTick(() => { 27 // 重新設置編輯器內容 28 me.editor.txt.html(v); 29 }); 30 } 31 } 32 } 33 editor: any = null; 34 // 標識是否正在設置數據,避免重復觸發事件 35 isUp: Boolean = false; 36 mounted() { 37 const me = this as any, 38 // eslint-disable-next-line new-cap 39 editor = new wangEditor(me.$el.firstChild) as any; 40 // zIndex配置小點,避免影響其他組件 41 editor.config.zIndex = 100; 42 // 配置 onchange 回調函數,將數據同步到 vue 中 43 editor.config.onchange = (newHtml: string) => { 44 me.isUp = true; 45 me.setValue(newHtml); 46 }; 47 48 // 創建編輯器 49 editor.create(); 50 me.editor = editor; 51 } 52 beforeDestroy() { 53 // 調用銷毀 API 對當前編輯器實例進行銷毀 54 this.editor.destroy(); 55 this.editor = null; 56 } 57 } 58 </script> 59 <style scoped lang="scss"> 60 ::v-deep.editor-el { 61 // 格式刷功能激活圖標樣式 62 .w-e-menu.w-e-active { 63 color: red; 64 } 65 } 66 </style>
效果大概是這樣