How to Create a Medium-Like Highlight Menu in Vue

In our example, it would be the <p> tag between <highlightable></highlightable>.Then, let's add mounted and beforeDestroy hook functions.mounted () { window.addEventListener('mouseup', this.onMouseup)},beforeDestroy () { window.removeEventListener('mouseup', this.onMouseup)}We use these to listen for mouseup event, which we handle inside onMouseup method.Now, let's create onMouseup method.methods: { onMouseup () { const selection = window.getSelection() const selectionRange = selection.getRangeAt(0) // startNode is the element that the selection starts in const startNode = selectionRange.startContainer.parentNode // endNode is the element that the selection ends in const endNode = selectionRange.endContainer.parentNode // if the selected text is not part of the highlightableEl (i.e. <p>) // OR // if startNode !== endNode (i.e. the user selected multiple paragraphs) // Then // Don't show the menu (this selection is invalid) if (!startNode.isSameNode(this.highlightableEl) || !startNode.isSameNode(endNode)) { this.showMenu = false return } // Get the x, y, and width of the selection const { x, y, width } = selectionRange.getBoundingClientRect() // If width === 0 (i.e. no selection) // Then, hide the menu if (!width) { this.showMenu = false return } // Finally, if the selection is valid, // set the position of the menu element, // set selectedText to content of the selection // then, show the menu this.x = x + (width / 2) this.y = y + window.scrollY – 10 this.selectedText = selection.toString() this.showMenu = true }}Now let's update the template of Highlightable.vue to reflect the new changes.<template> <div> <div v-show="showMenu" class="menu" :style="{ left: `${x}px`, top: `${y}px` }" @mousedown.prevent="" > <span class="item" @mousedown.prevent="handleAction('share')" > Share </span> <span class="item" @mousedown.prevent="handleAction('highlight')" > Highlight </span> <!– You can add more buttons here –> </div> <!– The insterted text should be displayed here –> <slot/> </div></template>The changes are:Applied the positions to the menu element.Added @mousedown.prevent="" to the menu element to prevent the menu from closing when clicking inside it.Added @mousedown.prevent="handleAction('share')" on share button to handle the clicked action..The same is for the highlight action.Note that we're using mousedown event instead of click to prevent the text from getting unselected — which would cause the menu to close.The last thing we have to do is add the handleAction method.handleAction (action) { this.$emit(action, this.selectedText)}This method emits the action event and passes the selected text along with it..(We used this event in App.vue, remember?)With that, we're done!.Now you have a reusable component that you can use to show a highlight menu for the selected text, just like Medium does.If you liked this tutorial, you may also like the other articles I have on my blog.. More details

Leave a Reply