在日常的学习和工作中,我们经常需要处理大量的文本信息。为了更好地理解和记忆这些信息,标记重点、添加注释以及复制内容是必不可少的操作。本文将介绍一个基于 HTML、CSS和 JavaScript 实现的高亮划线笔记工具,它能够帮助用户高效地进行文本阅读与标注。
效果演示


功能概览
本项目主要包含以下核心功能:
- 划线:允许用户对文本中的关键部分进行下划线标记。
- 记笔记:用户可以为选中的文本添加详细的注释,并且这些注释会被保存起来以便后续查看。
- 复制:一键复制选中的文本到剪贴板,方便快速分享或粘贴使用。
页面结构
主要内容区域
html
<div class="content">
<h1>高亮划线笔记工具</h1>
<p>本工具专为文本阅读与标注设计,支持划线高亮、添加笔记和复制选中内容。只需简单操作,即可提升您的阅读效率与学习体验。</p>
<p>无论是在学习资料中标注重点、整理研究内容,还是在文档中记录想法,高亮划线笔记工具都能助您高效掌握关键信息。</p>
</div>
工具栏
文本被选中后弹出工具栏,提供用户操作入口,包含三个功能按钮:划线、记笔记和复制。根据用户的鼠标选择动态显示在选中文本的上方,方便快速访问相关功能。
html
<div id="toolbar">
<button id="underline-btn">划线</button>
<button id="note-btn">记笔记</button>
<button id="copy-btn">复制</button>
</div>
笔记弹窗
当用户点击已高亮的文本时,显示对应的笔记内容和操作按钮。包含被高亮的原文和添加的笔记内容。提供"编辑"和"删除"按钮,用于对笔记进行修改或移除操作。
html
<div id="note-popup" class="note-popup">
<div class="note-content"></div>
<div class="note-text"></div>
<div class="note-actions">
<button class="edit">编辑</button>
<button class="delete">删除</button>
</div>
</div>
笔记列表区域
展示用户添加的所有笔记内容。通过 js 动态插入笔记条目。每个笔记包含高亮的原文、用户输入的注释以及创建时间。
html
<div id="notes-container">
<h2>我的笔记</h2>
<div id="notes-list"></div>
</div>
核心功能实现
选中文本后显示工具栏
监听 content 区域的鼠标释放事件(mouseup),用于检测用户是否选中了文本。当检测到有效选中文本时,显示工具栏并将其定位在选中文本的上方中间位置。
js
contentElem.addEventListener('mouseup', function(e) {
selectedText = window.getSelection().toString().trim();
if (selectedText.length > 0) {
// 获取选中的范围
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectedRange = selection.getRangeAt(0);
}
// 定位工具栏
toolbar.style.display = 'block';
// 获取选中范围的边界矩形
const rangeRect = selectedRange.getBoundingClientRect();
const centerX = rangeRect.left + rangeRect.width / 2;
const centerY = rangeRect.top + rangeRect.height / 2;
// 设置工具栏位置为中心点
toolbar.style.left = (centerX - toolbar.offsetWidth / 2) + 'px';
toolbar.style.top = (e.pageY - 60) + 'px';
} else {
toolbar.style.display = 'none';
}
});
点击页面其他地方隐藏工具栏。
js
document.addEventListener('mousedown', function(e) {
if (!toolbar.contains(e.target)) {
toolbar.style.display = 'none';
}
});
划线功能
当用户点击工具栏的"划线"按钮时,在当前选中的文本周围插入一个带有 underline 样式的 span 元素,实现下划线效果。
为该下划线元素添加点击事件,点击后可移除划线(将文本还原为普通文本)。执行完划线操作后隐藏工具栏并清除文本选中状态。
js
underlineBtn.addEventListener('click', function() {
if (selectedRange && selectedText.length > 0) {
const span = document.createElement('span');
span.className = 'underline';
// 添加点击事件来删除划线
span.addEventListener('click', function(e) {
e.stopPropagation();
const parent = this.parentNode;
const text = document.createTextNode(this.textContent);
parent.replaceChild(text, this);
parent.normalize();
});
selectedRange.surroundContents(span);
toolbar.style.display = 'none';
// 清除选择状态
window.getSelection().removeAllRanges();
}
});
记笔记功能
当用户点击工具栏的"记笔记"按钮时,弹出输入框让用户为当前选中的文本添加笔记内容。点击确定保存后,将选中文本和用户输入的笔记内容展示在右侧笔记列表中。在正文区域插入一个带有高亮样式的 span,并绑定点击事件用于显示对应的笔记内容。
点击高亮文本时,会弹出包含原文和笔记内容的浮动窗口,支持查看、编辑或删除笔记。
js
noteBtn.addEventListener('click', function() {
if (selectedText.length > 0) {
const noteTextContent = prompt('为选中的文本添加笔记:', '');
if (noteTextContent !== null) {
const noteElement = document.createElement('div');
noteElement.className = 'note';
noteElement.innerHTML = `<div class="note-content">"${selectedText}"</div>
<div class="note-text">${noteTextContent}</div>
<small>${new Date().toLocaleString()}</small>`;
// 添加高亮效果
const highlight = document.createElement('span');
highlight.className = 'highlight';
highlight.textContent = selectedText;
highlight.dataset.noteId = notesList.children.length;
selectedRange.deleteContents();
selectedRange.insertNode(highlight);
// 添加点击事件显示笔记
highlight.addEventListener('click', function(e) {
e.stopPropagation();
currentNote = this;
noteContent.textContent = `"${this.textContent}"`;
noteText.textContent = notesList.children[this.dataset.noteId].querySelector('.note-text').textContent;
notePopup.style.left = e.pageX + 'px';
notePopup.style.top = e.pageY + 'px';
notePopup.classList.add('active');
});
notesList.appendChild(noteElement);
toolbar.style.display = 'none';
// 清除选择状态
window.getSelection().removeAllRanges();
}
}
});
笔记编辑功能
当用户点击弹窗中的"编辑"按钮时,弹出输入框并显示当前笔记内容。用户输入新内容后,会同时更新右侧笔记列表中对应的文本内容。弹窗中的笔记内容也会同步更新,实现即时反馈。
js
editBtn.addEventListener('click', function() {
const newText = prompt('编辑笔记:', noteText.textContent);
if (newText !== null) {
currentNote.dataset.noteId;
notesList.children[currentNote.dataset.noteId].querySelector('.note-text').textContent = newText;
noteText.textContent = newText;
}
});
删除笔记功能
当用户点击弹窗中的"删除"按钮时,从右侧笔记列表中移除对应的笔记条目。将页面中高亮显示的文本还原为普通文本(去除高亮样式)。同时隐藏笔记弹窗,完成删除操作。
js
deleteBtn.addEventListener('click', function() {
notesList.removeChild(notesList.children[currentNote.dataset.noteId]);
currentNote.parentNode.replaceChild(document.createTextNode(currentNote.textContent), currentNote);
notePopup.classList.remove('active');
});
扩展建议
- 数据持久化:使用 localStorage 实现本地持久化存储,或者集成后端 API,支持将笔记同步到服务器,实现跨设备访问。
- 支持多文档/章节管理:左侧添加文档/文章列表,支持切换不同内容进行标注。每个文档的笔记独立存储,互不影响。
- 增强笔记交互体验:添加标签分类、颜色标记等功能。支持富文本编辑器来输入更丰富的笔记内容。
- 搜索与回顾功能:添加搜索框,支持按关键词检索所有笔记。提供"复习模式",逐条展示笔记并隐藏原文,用于自测回顾。
- 快捷键支持:快速添加高亮、笔记,关闭弹窗或取消工具栏。
完整代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高亮划线笔记工具</title>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.content {
margin-bottom: 30px;
float: left;
width: 70%;
}
#toolbar {
position: fixed;
display: none;
background: #333;
border-radius: 4px;
padding: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 1000;
}
#toolbar button {
background: #444;
color: white;
border: none;
padding: 5px 10px;
margin: 0 2px;
border-radius: 3px;
cursor: pointer;
}
#toolbar button:hover {
background: #555;
}
.underline {
cursor: pointer;
position: relative;
border-bottom: 2px solid #ff6b6b;
}
.underline:hover::after {
content: "×";
position: absolute;
right: -15px;
top: -10px;
color: red;
font-size: 16px;
background: white;
border-radius: 50%;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 3px rgba(0,0,0,0.3);
}
#notes-container {
float: right;
width: 25%;
border-left: 1px solid #eee;
padding-left: 20px;
margin-top: 30px;
}
.note {
background: #f9f9f9;
padding: 10px;
margin-bottom: 10px;
border-left: 3px solid #4CAF50;
}
.note-text {
font-style: italic;
color: #555;
}
.note-content {
font-weight: bold;
}
.highlight {
background-color: #ffff00;
cursor: pointer;
}
.note-popup {
display: none;
position: absolute;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
z-index: 1001;
font-size: 14px;
line-height: 1.6;
min-width: 150px;
}
.note-popup.active {
display: block;
}
.note-popup .note-content {
font-weight: bold;
margin-bottom: 10px;
}
.note-popup .note-text {
font-style: italic;
color: #555;
margin-bottom: 15px;
}
.note-popup .note-actions {
display: flex;
justify-content: space-between;
}
.note-popup .note-actions button {
background: #4CAF50;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s ease;
}
.note-popup .note-actions button:hover {
background: #45a049;
}
.note-popup .note-actions button.delete {
background: #f44336;
}
.note-popup .note-actions button.delete:hover {
background: #d32f2f;
}
</style>
</head>
<body>
<div id="toolbar">
<button id="underline-btn">划线</button>
<button id="note-btn">记笔记</button>
<button id="copy-btn">复制</button>
</div>
<div id="note-popup" class="note-popup">
<div class="note-content"></div>
<div class="note-text"></div>
<div class="note-actions">
<button class="edit">编辑</button>
<button class="delete">删除</button>
</div>
</div>
<div id="content" class="content">
<h1>高亮划线笔记工具</h1>
<p>本工具专为文本阅读与标注设计,支持划线高亮、添加笔记和复制选中内容。只需简单操作,即可提升您的阅读效率与学习体验。</p>
<p>无论是在学习资料中标注重点、整理研究内容,还是在文档中记录想法,高亮划线笔记工具都能助您高效掌握关键信息。</p>
</div>
<div id="notes-container">
<h2>我的笔记</h2>
<div id="notes-list"></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const toolbar = document.getElementById('toolbar');
const contentElem = document.getElementById('content');
const underlineBtn = document.getElementById('underline-btn');
const noteBtn = document.getElementById('note-btn');
const copyBtn = document.getElementById('copy-btn');
const notesList = document.getElementById('notes-list');
const notePopup = document.getElementById('note-popup');
const noteContent = notePopup.querySelector('.note-content');
const noteText = notePopup.querySelector('.note-text');
const editBtn = notePopup.querySelector('.edit');
const deleteBtn = notePopup.querySelector('.delete');
let selectedText = '';
let selectedRange = null;
let currentNote = null;
// 显示工具栏
contentElem.addEventListener('mouseup', function(e) {
selectedText = window.getSelection().toString().trim();
if (selectedText.length > 0) {
// 获取选中的范围
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectedRange = selection.getRangeAt(0);
}
// 定位工具栏
toolbar.style.display = 'block';
// 获取选中范围的边界矩形
const rangeRect = selectedRange.getBoundingClientRect();
const centerX = rangeRect.left + rangeRect.width / 2;
const centerY = rangeRect.top + rangeRect.height / 2;
// 设置工具栏位置为中心点
toolbar.style.left = (centerX - toolbar.offsetWidth / 2) + 'px';
toolbar.style.top = (e.pageY - 60) + 'px';
} else {
toolbar.style.display = 'none';
}
});
// 点击页面其他地方隐藏工具栏
document.addEventListener('mousedown', function(e) {
if (!toolbar.contains(e.target)) {
toolbar.style.display = 'none';
}
});
// 划线功能
underlineBtn.addEventListener('click', function() {
if (selectedRange && selectedText.length > 0) {
const span = document.createElement('span');
span.className = 'underline';
// 添加点击事件来删除划线
span.addEventListener('click', function(e) {
e.stopPropagation();
const parent = this.parentNode;
const text = document.createTextNode(this.textContent);
parent.replaceChild(text, this);
parent.normalize();
});
selectedRange.surroundContents(span);
toolbar.style.display = 'none';
// 清除选择状态
window.getSelection().removeAllRanges();
}
});
// 复制功能
copyBtn.addEventListener('click', function() {
if (selectedText.length > 0) {
navigator.clipboard.writeText(selectedText)
.then(() => {
alert('已复制到剪贴板: ' + selectedText);
toolbar.style.display = 'none';
})
.catch(err => {
console.error('复制失败: ', err);
});
}
});
// 记笔记功能
noteBtn.addEventListener('click', function() {
if (selectedText.length > 0) {
const noteTextContent = prompt('为选中的文本添加笔记:', '');
if (noteTextContent !== null) {
const noteElement = document.createElement('div');
noteElement.className = 'note';
noteElement.innerHTML = `<div class="note-content">"${selectedText}"</div>
<div class="note-text">${noteTextContent}</div>
<small>${new Date().toLocaleString()}</small>`;
// 添加高亮效果
const highlight = document.createElement('span');
highlight.className = 'highlight';
highlight.textContent = selectedText;
highlight.dataset.noteId = notesList.children.length;
selectedRange.deleteContents();
selectedRange.insertNode(highlight);
// 添加点击事件显示笔记
highlight.addEventListener('click', function(e) {
e.stopPropagation();
currentNote = this;
noteContent.textContent = `"${this.textContent}"`;
noteText.textContent = notesList.children[this.dataset.noteId].querySelector('.note-text').textContent;
notePopup.style.left = e.pageX + 'px';
notePopup.style.top = e.pageY + 'px';
notePopup.classList.add('active');
});
notesList.appendChild(noteElement);
toolbar.style.display = 'none';
// 清除选择状态
window.getSelection().removeAllRanges();
}
}
});
// 编辑笔记
editBtn.addEventListener('click', function() {
const newText = prompt('编辑笔记:', noteText.textContent);
if (newText !== null) {
currentNote.dataset.noteId;
notesList.children[currentNote.dataset.noteId].querySelector('.note-text').textContent = newText;
noteText.textContent = newText;
}
});
// 删除笔记
deleteBtn.addEventListener('click', function() {
notesList.removeChild(notesList.children[currentNote.dataset.noteId]);
currentNote.parentNode.replaceChild(document.createTextNode(currentNote.textContent), currentNote);
notePopup.classList.remove('active');
});
// 点击页面其他地方隐藏笔记弹窗
document.addEventListener('mousedown', function(e) {
if (!notePopup.contains(e.target)) {
notePopup.classList.remove('active');
}
});
});
</script>
</body>
</html>