vue 遞歸創建菜單
by Taha Shashtari
由Taha Shashtari
如何在Vue中創建類似中等的突出顯示菜單 (How to Create a Medium-Like Highlight Menu in Vue)
A cool feature in Medium is the highlight menu that pops up when you select some text. This menu contains buttons that allow you to perform certain actions on the selected text like highlight and share.
在一個很酷的功能中是當你選擇一些文本在彈出的菜單中的亮點。 此菜單包含一些按鈕,這些按鈕使您可以對所選文本執行某些操作,例如突出顯示和共享。
If you like this feature and you want to have it in your site, I’m going to show you how to create a reusable component that enables this behavior on the text it contains.
如果您喜歡此功能,并且希望在您的網站中使用它,我將向您展示如何創建可重用的組件,以在其包含的文本上啟用此行為。
You can try a live demo on CodePen:
您可以在CodePen上嘗試現場演示:
View the CodePen here.
在此處查看CodePen。
使用Vue CLI 3創建一個新項目 (Creating a new project with Vue CLI 3)
With Vue CLI 3 instant prototyping, we can rapidly run a Vue app with just a single *.vue
file.
借助Vue CLI 3 即時原型 ,我們可以僅使用一個*.vue
文件快速運行Vue應用程序。
Note that this is only used for creating prototypes, not for production.
請注意,這僅用于創建原型,而不用于生產。
First, make sure that you have this installed globally:
首先,請確保已在全球范圍內安裝了此軟件:
npm install -g @vue/cli-service-global
npm install -g @vue/cli-service-global
In this app, we’ll only need two files: App.vue and Highlightable.vue.
在此應用程序中,我們僅需要兩個文件: App.vue和Highlightable.vue 。
Highlightable.vue is our reusable highlight menu component. And App.vue is the main page component.
Highlightable.vue是我們可重用的突出顯示菜單組件。 而App.vue是主要的頁面組件。
Create both files in any directory you want; then, run vue serve
on App.vue.
在所需的任何目錄中創建兩個文件; 然后,在App.vue上運行vue serve
。
vue serve App.vue
實施App.vue (Implementing App.vue)
In App.vue, we'll add two paragraphs. One that can be highlighted, and one that can't.
在App.vue中 ,我們將添加兩個段落。 一種可以突出顯示,另一種不能突出顯示。
We’ll also import and use Highlightable.vue before even creating it. (This is helpful to see how we're going to use it.)
我們甚至會在創建之前導入并使用Highlightable.vue 。 (這有助于了解我們將如何使用它。)
Here’s how it should look in the end:
最終效果如下:
<template> <div class="app"> <highlightable @share="onShare" @highlight="onHighlight" > <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet at debitis deserunt, optio rem eaque obcaecati non possimus nisi assumenda architecto exercitationem dolore quo praesentium, deleniti reiciendis sed ab nihil! </p> </highlightable> <p> <strong>This paragraph can't be highlighted.</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Labore ipsam repellat, fugiat aut ex incidunt ut quisquam quasi consequatur ducimus quo in, cum soluta eos dolores tempore unde voluptate modi. <;/p> </div></template><script>import Highlightable from './Highlightable'export default { components: { Highlightable }, methods: { onShare (text) { console.log('share:', text) }, onHighlight (text) { console.log('highlight:', text) } }}</script><style scoped>* { box-sizing: border-box;}.app { width: 800px; margin: 40px auto; padding: 10px; font-family: Verdana; color: #333; width: 100%;}p { line-height: 1.5;}</style>
As you can see above, we're handling two events from Highlightable. These two events are the actions of the buttons in the highlight menu. These are just examples. You can change them to whatever you want.
正如您在上面看到的,我們正在處理Highlightable中的兩個事件。 這兩個事件是高亮菜單中按鈕的動作。 這些僅僅是示例。 您可以將它們更改為任何您想要的。
實施Highlightable.vue (Implementing Highlightable.vue)
The template section consists of two parts: the menu element with buttons and <slo
t/> to display the text.
模板部分由兩部分組成:帶按鈕的菜單元素和用于顯示文本的<slo
t />。
Let’s start with this code in the template:
讓我們從模板中的以下代碼開始:
<template> <div> <div v-show="showMenu" class="menu" > <span class="item"> Share </span> <span class="item"> Highlight </span> <!-- You can add more buttons here --> </div> <!-- The insterted text should be displayed here --> <slot/> </div></template>
Note that we're using showMenu
, which we haven't created yet, to determine if we should display the menu.
請注意,我們使用尚未創建的showMenu
來確定是否顯示菜單。
Now let's move to the styling part.
現在,讓我們轉到樣式部分。
Add the following CSS to <sty
le> section:
將以下CSS添加到<sty
le>部分:
<style scoped>.menu { height: 30px; padding: 5px 10px; background: #333; border-radius: 3px; position: absolute; top: 0; left: 0; transform: translate(-50%, -100%); transition: 0.2s all; display: flex; justify-content: center; align-items: center;}.menu:after { content: ''; position: absolute; left: 50%; bottom: -5px; transform: translateX(-50%); width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid #333;}.item { color: #FFF; cursor: pointer;}.item:hover { color: #1199ff;}.item + .item { margin-left: 10px;}</style>
Nothing is too complex here. .menu
is for the highlight menu. menu:after
is for the little triangle (arrow) in the bottom center of the menu.
這里沒有什么太復雜的。 .menu
用于突出顯示菜單。 menu:after
是menu:after
底部中心的小三角形(箭頭)。
One important thing to note here is that .menu
has an absolute
position. We need this to position it above the selected text.
這里要注意的重要一件事是.menu
具有absolute
位置。 我們需要將其放置在所選文本上方。
Finally, let's move to the <scri
pt> section.
最后,讓我們進入<scri
pt>部分。
Let's start with the data.
讓我們從data開始。
export default { data () { return { x: 0, y: 0, showMenu: false, selectedText: '' } }}
x
andy
are for positioning the menu.x
和y
用于放置菜單。showMenu
to show/hide the menu.showMenu
顯示/隱藏菜單。selectedText
will contain the actual content of the selected text.selectedText
將包含所選文本的實際內容。
Now, let's move to computed.
現在,讓我們開始計算 。
computed: { highlightableEl () { return this.$slots.default[0].elm }}
We only have a single computed property that returns the element used in the slot section of Highlightable. In our example, it would be the <
;p> tag between <highlightable><
/highlightable>.
我們只有一個計算屬性,該屬性返回Highlightable的插槽部分中使用的元素。 在我們的示例中,將是tween <highlightable><
/ highlightable>之間的<
; p>標記。
Then, let's add mounted
and beforeDestroy
hook functions.
然后,讓我們添加mounted
和beforeDestroy
掛鉤函數。
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.
我們使用它們來監聽mouseup
事件,該事件在onMouseup
方法內部處理。
Now, let's create onMouseup
method.
現在,讓我們創建onMouseup
方法。
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.
現在,讓我們更新Highlightable.vue的模板以反映新的更改。
<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.在菜單元素中添加了
@mousedown.prevent=""
,以防止在內部單擊時關閉菜單。Added
@mousedown.prevent="handleAction('share')"
on share button to handle the clicked action. The same is for the highlight action.在共享按鈕上添加了
@mousedown.prevent="handleAction('share')"
來處理單擊的操作。 高亮操作也是如此。
Note that we're using mousedown
event instead of click
to prevent the text from getting unselected — which would cause the menu to close.
請注意,我們使用mousedown
事件而不是click
事件來防止未選中文本-這將導致菜單關閉。
The last thing we have to do is add the handleAction
method.
我們要做的最后一件事是添加handleAction
方法。
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?)
此方法發出action
事件,并將所選文本與之一起傳遞。 (我們在App.vue中使用了此事件,還記得嗎?)
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.
這樣,我們就完成了! 現在,您有了一個可重用的組件,可以像Medium一樣使用它來顯示所選文本的突出顯示菜單。
Thanks for reading! By the way, I’m writing a book on how to build a complete single-page application from scratch using Vue. Check out the book’s landing page if you’re interested in learning more about what the book will cover:
謝謝閱讀! 順便說一句,我正在寫一本關于如何使用Vue從頭構建完整的單頁應用程序的書。 如果您有興趣了解有關該書涵蓋內容的更多信息,請查看該書的登錄頁面:
翻譯自: https://www.freecodecamp.org/news/how-to-create-a-medium-like-highlight-menu-in-vue-dc515f2dddef/
vue 遞歸創建菜單