Implementing a Popper That Follows a Text Selection Area
Utilizing the concept of Virtual Elements to address popper positioning issues and properly handling mouse events are key to flawlessly realizing this functional component.
Requirement Analysis
The requirement is to display an icon beneath a selected piece of text, with the icon’s click event triggering an API call to fetch and display the definition of the selected word(s). The original project utilized the jQuery UI library webui-popper , but this implementation utilizes Vue 3 in conjunction with the UI library element-plus for the rewrite.
Conventional Usage of el-popper
Conventionally, el-popper is attached to a specific element serving as its reference:
It is worth noting that el-popper is essentially a wrapper around popper.js, now upgraded to floating-ui. The official documentation provides inspiration, particularly the use of virtual element.
Handling Virtual Elements
The initial intuition is to encapsulate the selected text within a tag, treating it as a virtual element and also serving as the el-popper reference:
Crucially, this virtual element must provide positional information through its getBoundingClientRect method. This can be achieved quite straightforwardly:
Displaying the Icon
With the positioning and popper display resolved, let’s incorporate the icon:
Although the icon is now displayed, it appears enclosed within a modal-like container, which does not meet our desired effect.
Resolving Encapsulation Styles
To address this issue, follow these steps:
- Remove the arrow: :show-arrow=“false”
- Make the wrapping element transparent: :popper-class=“customPopperClass”, then define the custom styles:
Solving Auto-Dissipation Issue
After displaying the icon, we do not want the popper to disappear automatically; thus, we need to change its trigger mode to ‘manual’. For newer versions of element-plus, omitting the trigger attribute is equivalent to ‘manual’ (this detail is not clearly documented, discovered through encountering issues and examining the source code).
Currently, the implemented approach does not prevent the popper from disappearing. Using trigger=“click” works, but considering our usage of a virtual element, introducing a click-based trigger could introduce other complications. As a compromise, set a sufficiently long display duration: :hide-after=“1000000”
Additionally, we need to handle the finer details of disappearance—specifically, closing the popper when clicking outside of it.
For this purpose, we will utilize vueuse:
We’ll then determine whether the click event occurs within the popper area:
We include the isPointerInRect check to ensure that clicking on the selected text itself does not close the popper:
Conclusion
Implementing this component is not overly complex, and the process introduced new concepts such as Virtual Elements and getBoundingClientRect. It was an enjoyable learning experience, hence this write-up.
BTW,
During most of the time spent developing this solution, I relied on the plugin TONGYI Lingma. Many of its suggestions were directly applicable. However, for more accurate responses, it is best to specify requirements like using <script setup lang=“ts”> for component encapsulation, as it defaults to providing Vue 2 syntax.