This article uses vue, and adds mouse click events and some small page optimizations Basic structure Create a sandBox.vue file to write the basic structure of the function <div class="content"> <!--Text box--> <div class="editor" ref="divRef" contenteditable @keyup="handkeKeyUp" @keydown="handleKeyDown" ></div> <!--Options--> <AtDialog v-if="showDialog" :visible="showDialog" :position="position" :queryString="queryString" @onPickUser="handlePickUser" @onHide="handleHide" @onShow="handleShow" ></AtDialog> </div> <script> import AtDialog from '../components/AtDialog' export default { name: 'sandBox', components: { AtDialog }, data () { return { node: '', // Get the node user: '', // The content of the selected item endIndex: '', // The last cursor position queryString: '', // Search value showDialog: false, // Whether to display the pop-up window position: { x: 0, y: 0 }//Popup window display position} }, methods: { // Get the cursor position getCursorIndex () { const selection = window.getSelection() return selection.focusOffset //Select the offset of focusNode at the beginning}, // Get the node getRangeNode () { const selection = window.getSelection() return selection.focusNode // selected end node}, // The location where the pop-up window appears getRangeRect () { const selection = window.getSelection() const range = selection.getRangeAt(0) // is a generic object for managing selection ranges const rect = range.getClientRects()[0] // Select some text and get the range of the selected text const LINE_HEIGHT = 30 return { x: rect.x, y: rect.y + LINE_HEIGHT } }, // Whether to display @ showAt() { const node = this.getRangeNode() if (!node || node.nodeType !== Node.TEXT_NODE) return false const content = node.textContent || '' const regx = /@([^@\s]*)$/ const match = regx.exec(content.slice(0, this.getCursorIndex())) return match && match.length === 2 }, // Get @user getAtUser () { const content = this.getRangeNode().textContent || '' const regx = /@([^@\s]*)$/ const match = regx.exec(content.slice(0, this.getCursorIndex())) if (match && match.length === 2) { return match[1] } return undefined }, // Create a label createAtButton (user) { const btn = document.createElement('span') btn.style.display = 'inline-block' btn.dataset.user = JSON.stringify(user) btn.className = 'at-button' btn.contentEditable = 'false' btn.textContent = `@${user.name}` const wrapper = document.createElement('span') wrapper.style.display = 'inline-block' wrapper.contentEditable = 'false' const spaceElem = document.createElement('span') spaceElem.style.whiteSpace = 'pre' spaceElem.textContent = '\u200b' spaceElem.contentEditable = 'false' const clonedSpaceElem = spaceElem.cloneNode(true) wrapper.appendChild(spaceElem) wrapper.appendChild(btn) wrapper.appendChild(clonedSpaceElem) return wrapper }, replaceString (raw, replacer) { return raw.replace(/@([^@\s]*)$/, replacer) }, // Insert @ tag replaceAtUser (user) { const node = this.node if (node && user) { const content = node.textContent || '' const endIndex = this.endIndex const preSlice = this.replaceString(content.slice(0, endIndex), '') const restSlice = content.slice(endIndex) const parentNode = node.parentNode const nextNode = node.nextSibling const previousTextNode = new Text(preSlice) const nextTextNode = new Text('\u200b' + restSlice) // Add 0 wide characters const atButton = this.createAtButton(user) parentNode.removeChild(node) // Insert in the text box if (nextNode) { parentNode.insertBefore(previousTextNode, nextNode) parentNode.insertBefore(atButton, nextNode) parentNode.insertBefore(nextTextNode, nextNode) } else { parentNode.appendChild(previousTextNode) parentNode.appendChild(atButton) parentNode.appendChild(nextTextNode) } // Reset the cursor position const range = new Range() const selection = window.getSelection() range.setStart(nextTextNode, 0) range.setEnd(nextTextNode, 0) selection.removeAllRanges() selection.addRange(range) } }, //Keyboard up eventhandkeKeyUp () { if (this.showAt()) { const node = this.getRangeNode() const endIndex = this.getCursorIndex() this.node = node this.endIndex = endIndex this.position = this.getRangeRect() this.queryString = this.getAtUser() || '' this.showDialog = true } else { this.showDialog = false } }, //Keyboard press event handleKeyDown (e) { if (this.showDialog) { if (e.code === 'ArrowUp' || e.code === 'ArrowDown' || e.code === 'Enter') { e.preventDefault() } } }, // Hide the selection box after inserting the tag handlePickUser (user) { this.replaceAtUser(user) this.user = user this.showDialog = false }, //Hide the selection box handleHide () { this.showDialog = false }, // Display the selection box handleShow () { this.showDialog = true } } } </script> <style scoped lang="scss"> .content { font-family: sans-serif; h1{ text-align: center; } } .editor { margin: 0 auto; width: 600px; height: 150px; background: #fff; border: 1px solid blue; border-radius: 5px; text-align: left; padding: 10px; overflow:auto; line-height: 30px; &:focus { outline: none; } } </style> If a click event is added, the node and cursor position must be obtained in the [Keyboard Up Event] and saved to the data //Keyboard up eventhandkeKeyUp () { if (this.showAt()) { const node = this.getRangeNode() // Get the node const endIndex = this.getCursorIndex() // Get the cursor position this.node = node this.endIndex = endIndex this.position = this.getRangeRect() this.queryString = this.getAtUser() || '' this.showDialog = true } else { this.showDialog = false } }, Create a new component and edit the pop-up options <template> <div class="wrapper" :style="{position:'fixed',top:position.y +'px',left:position.x+'px'}"> <div v-if="!mockList.length" class="empty">No search results</div> <div v-for="(item,i) in mockList" :key="item.id" class="item" :class="{'active': i === index}" ref="usersRef" @click="clickAt($event,item)" @mouseenter="hoverAt(i)" > <div class="name">{{item.name}}</div> </div> </div> </template> <script> const mockData = [ { name: 'HTML', id: 'HTML' }, { name: 'CSS', id: 'CSS' }, { name: 'Java', id: 'Java' }, { name: 'JavaScript', id: 'JavaScript' } ] export default { name: 'AtDialog', props: { visible: Boolean, position: Object, queryString: String }, data () { return { users: [], index: -1, mockList: mockData } }, watch: queryString (val) { val ? this.mockList = mockData.filter(({ name }) => name.startsWith(val)) : this.mockList = mockData.slice(0) } }, mounted () { document.addEventListener('keyup', this.keyDownHandler) }, destroyed () { document.removeEventListener('keyup', this.keyDownHandler) }, methods: { keyDownHandler (e) { if (e.code === 'Escape') { this.$emit('onHide') return } //Keyboard pressed => ↓ if (e.code === 'ArrowDown') { if (this.index >= this.mockList.length - 1) { this.index = 0 } else { this.index = this.index + 1 } } //Keyboard pressed => ↑ if (e.code === 'ArrowUp') { if (this.index <= 0) { this.index = this.mockList.length - 1 } else { this.index = this.index - 1 } } //Keyboard pressed => Enterif (e.code === 'Enter') { if (this.mockList.length) { const user = { name: this.mockList[this.index].name, id: this.mockList[this.index].id } this.$emit('onPickUser', user) this.index = -1 } } }, clickAt (e, item) { const user = { name: item.name, id: item.id } this.$emit('onPickUser', user) this.index = -1 }, hoverAt (index) { this.index = index } } } </script> <style scoped lang="scss"> .wrapper { width: 238px; border: 1px solid #e4e7ed; border-radius: 4px; background-color: #fff; box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); box-sizing: border-box; padding: 6px 0; } .empty{ font-size: 14px; padding: 0 20px; color: #999; } .item { font-size: 14px; padding: 0 20px; line-height: 34px; cursor: pointer; color: #606266; &.active { background: #f5f7fa; color: blue; .id { color: blue; } } &:first-child { border-radius: 5px 5px 0 0; } &:last-child { border-radius: 0 0 5px 5px; } .id { font-size: 12px; color: rgb(83, 81, 81); } } </style> The above is the details of how to implement the @人 function through Vue. For more information about Vue @人 function, please pay attention to other related articles on 123WORDPRESS.COM! You may also be interested in:
|
<<: Bootstrap 3.0 study notes CSS related supplement
>>: Linux system file sharing samba configuration tutorial
Preface I looked at the previously published arti...
Table of contents 1. Regular expression creation ...
mysql dirty pages Due to the WAL mechanism, when ...
Enter net start mysql in cmd and the prompt is: T...
The specific code for encapsulating the image cap...
Call How to call Amap API? The official open docu...
Encryption and decryption are an important means ...
1. To optimize the query, try to avoid full table...
In the field of data analysis, database is our go...
Difference between HTML and XHTML 1. XHTML elemen...
Preface vsftp is an easy-to-use and secure ftp se...
When the amount of data in MySQL is large, limit ...
01. Command Overview The tr command can replace, ...
Table of contents getApp() Define variables at th...
In the previous article, we introduced the detail...