Vanilla JavaScript:如何动态创建一个按钮,在用户选择一些文本后显示,然后对该文本执行某些操作?

Vanilla JavaScript: How to dynamically create a button that shows up after user has selected some text and then do something with that text?

我正在开发一个小项目,用户可以在该项目中上传他们的 .docx 文件,然后直接在应用程序中读取这些文件(以及来自其他用户的文件)。为此,我从 docx 中提取文本并将其显示在单独视图和 html 页面的 div 中。现在,我想让我的用户可以选择 select 来自这个 div 的一些文本(并且只有这个 div),当他们 select 编辑它时,我会喜欢显示一个按钮,该按钮悬停在他们可以单击的文本上。此按钮用于将 selected 文本添加到他们的笔记中,类似于当我们 select 一些文本时 MS Edge 自动执行的操作(它添加了这三个点,然后打开一个菜单进行复制和其余部分) .或者更准确地说,如果您熟悉 Coursera 网站 - 当课程中的某些文本被 selected 时,会出现一个 'Save Note' 按钮,单击它会将 selected 文本添加到您的不需要用户做任何其他事情的笔记,他们可以在他们访问笔记时找到保存的笔记(下面的照片显示了这个按钮的作用)。

Coursera 'Save Note' button example

但是,我不确定如何实现它。我想我会使用 window.getSelection,然后将 selection 存储在一些 const 中,然后通过 fetch 将其发送到我的服务器以将其添加到 Notes 模型(我使用的是 Django)。但我不知道如何实施,甚至不知道从哪里开始。仅当 selection 被 selected 时,如何甚至像这样使按钮悬停在其他文本上?非常感谢任何形式的帮助!!注意:如果可能的话,我想在 Vanilla JS 中使用它,因为我还不熟悉 React 或其他 libraries/frameworks.

JavaScript 中有一个函数可以检索当前选定的文本:

function getSelectionText() {
    if (document.getSelection().toString().length === 0) {
//do nothing
    } else if (document.getSelection().toString().length > 0) {
        //fire button function
    }
    return text;
}

我相信没有仅在选择文本时才触发的功能,因此该功能应该 运行 持续,或者当有 dom “mouseup” 事件时。

将问题分解为任务:

  1. 检测选择开始和结束 - 可以分别使用onmouseup and onmousedown完成。
  • 使用EventHandler可用的事件对象查找所选区域的坐标

  • onmousedown 存储选择开始的坐标

  • onmouseup获取选择结束的坐标

  • 使用起始坐标和结束坐标定位复制按钮(使用CSS

  1. 获取选定的文本 - window.getSelection可用于在按下按钮时获取选定的文本。

一旦检测到选择并且所选文本可用,就可以将其推送到服务器。

像这样:

const copyBtn = document.createElement("button");
copyBtn.innerHTML = "Press this button to log selected text";
copyBtn.style.position = "absolute";
copyBtn.style.display = "none";
copyBtn.onclick = function(evt) {
  console.log(window.getSelection().toString())
}
document.body.appendChild(copyBtn);

const paragraph = document.querySelector('p');
let startX = 0;
let startY = 0;

function mouseDown(evt) {
  console.log(`Selection started at : ${evt.clientX}, ${evt.clientY}`);
  startX = evt.clientX;
  startY = evt.clientY;

  copyBtn.style.display = "none";
}

function mouseUp(evt) {
  console.log(`Selection ended at : ${evt.clientX}, ${evt.clientY}`);

  copyBtn.style.display = "block";
  copyBtn.style.top = `${Math.min(startY, evt.clientY)}px`;
  copyBtn.style.left = `${(startX + evt.clientX) / 2}px`;

}
paragraph.onmouseup = mouseUp;
paragraph.onmousedown = mouseDown
<!DOCTYPE html>
<html>
<body>

<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>

</body>
</html>

必须回答我自己的问题,以供将来参考。 多亏了 Tyler Durden 和 Endoxos 的解决方案,在玩了几个小时之后,现在的代码(大部分)完成了我想要它做的事情(它也被评论了为了更容易理解这个答案):

/* Read view - dynamically adding Save Note button after selection of text */
document.addEventListener('DOMContentLoaded', function() {
    /* Use this functions only in a div that contains displayed contents of files */
    const content = document.querySelector('#docContent');
    /* Create and append button */
    const noteBtn = document.createElement('button');
    noteBtn.innerHTML = 'Save Note';
    noteBtn.style.position = 'absolute';
    noteBtn.style.display = 'none';
    noteBtn.className = 'btn btn-sm btn-danger';

    content.appendChild(noteBtn);

    let startX = 0;
    let startY = 0;

    /* On mousedown only save starting X and Y, but relevant to entire page, 
     not the client X and Y, which causes button to stay on top part of doc,
     even if we want to select text from bottom part. */
    content.addEventListener('mousedown', function(evt){
        startX = evt.pageX;
        startY = evt.pageY;
    });
    
    /* On mouse up, we check if the end X and Y differ from starting
    and if, we place the button to the end of the selection, where user's
    mouse will naturally be, after making the selection. This works on every
    part of the page and dom, except on the far right side (if selection ends
    on the endpoint on right side, that is), and for these cases one might make
    calculations and for those cases just reverse the direction of button, but
    I can't be bothered to do so today, maybe tomorrow... Also, if the start and
    end X and Y do not differ, most likely user wanted to click somewhere to 'hide'
    the popped up button, so we just set its display to none in such case*/
    content.addEventListener('mouseup', function(evt) {  
        if (evt.pageX != startX && evt.pageY != startY ) {
            noteBtn.style.top = `${evt.pageY}px`;
            noteBtn.style.left = `${evt.pageX}px`;
            noteBtn.style.display = 'block';
        } else {
            noteBtn.style.display = 'none';
        }
    });

    /* Finally, we add event listener for clicks on button, and when the button is
    clicked we save the text to const, and pass that to our view in Django (in this 
    case there is csrf_exempt, but normally one would do that too...) */
    noteBtn.addEventListener('click', function() {
        const note = document.getSelection().toString();
        const id = content.querySelector('.reading_content_id').value;
        fetch(`/add_note/${id}`, {
            method: 'POST',
            body: JSON.stringify({
                note:`${note}`
            })
        }).then (function() {
            document.getSelection().collapseToEnd();
            noteBtn.style.display = 'none';
        });
    });
});