Compare commits

..

3 Commits

@ -0,0 +1,114 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>二维码解码工具</title>
<link
rel="stylesheet"
href="style.css"
/>
<style>
/* 为解码页面添加特定样式 */
#image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
padding: 10px;
border: 2px dashed #dfe1e6;
border-radius: 4px;
min-height: 100px;
align-content: flex-start;
}
.preview-item {
position: relative;
width: 120px;
height: 120px;
border: 1px solid #ccc;
border-radius: 4px;
overflow: hidden;
cursor: move;
}
.preview-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.preview-item .remove-btn {
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 12px;
line-height: 20px;
text-align: center;
}
#result-container {
margin-top: 20px;
}
#result-text {
width: 100%;
height: 150px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>二维码解码 & 拼接工具</h1>
<p>上传多个二维码图片,拖拽排序,然后一键解码并拼接内容。</p>
<div>
<label
for="qr-input"
class="button-like"
>选择二维码图片</label
>
<input
type="file"
id="qr-input"
multiple
accept="image/*"
style="display: none"
/>
</div>
<div id="image-preview-container">
<!-- 上传的图片预览将显示在这里 -->
</div>
<button id="decode-btn">解码 & 拼接</button>
<div id="result-container">
<h3>解码结果:</h3>
<textarea
id="result-text"
readonly
placeholder="解码后的内容将显示在这里..."
></textarea>
<button id="copy-btn">复制结果</button>
</div>
<a
href="/index.html"
class="nav-link"
>返回二维码生成器</a
>
</div>
<!-- JS 库和逻辑脚本将在这里引入 -->
<script src="./js/jsQR.js"></script>
<script src="./js/Sortable.min.js"></script>
<script src="./js/decode.js"></script>
</body>
</html>

@ -28,11 +28,18 @@
<div id="qrcode-container"> <div id="qrcode-container">
<!-- 二维码将在这里生成 --> <!-- 二维码将在这里生成 -->
</div> </div>
<a
href="/decode.html"
class="nav-link"
style="text-align: center; display: block; margin-top: 20px"
>前往二维码解码器 &rarr;</a
>
</div> </div>
<!-- 引入 qrcode.js 库 --> <!-- 引入 qrcode.js 库 -->
<script src="qrcode.min.js"></script> <script src="js/qrcode.min.js"></script>
<!-- 引入我们自己的逻辑脚本 --> <!-- 引入我们自己的逻辑脚本 -->
<script src="main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

@ -0,0 +1,199 @@
// decode.js
// This file will contain the logic for the QR code decoding feature.
document.addEventListener('DOMContentLoaded', () => {
console.log('解码页面脚本已加载。');
// 获取页面上的关键元素
const qrInput = document.getElementById('qr-input');
const imagePreviewContainer = document.getElementById('image-preview-container');
const decodeBtn = document.getElementById('decode-btn');
const resultText = document.getElementById('result-text');
const copyBtn = document.getElementById('copy-btn');
// 初始化 SortableJS为预览容器开启拖拽排序功能
new Sortable(imagePreviewContainer, {
animation: 150, // 拖拽动画的毫秒数
ghostClass: 'sortable-ghost', // 拖拽时占位元素的类名
});
// 监听文件输入框的 change 事件
qrInput.addEventListener('change', handleFileSelect);
/**
* 处理用户选择的文件
* @param {Event} event - input change 事件对象
*/
function handleFileSelect(event) {
// 获取用户选择的文件列表
const files = event.target.files;
if (!files) {
return;
}
// 遍历所有选择的文件
for (const file of files) {
// 确保文件是图片类型
if (!file.type.startsWith('image/')) {
continue;
}
const reader = new FileReader();
// 文件读取成功后的回调
reader.onload = function (e) {
createPreview(e.target.result, file.name);
};
// 以 Data URL 的格式读取文件
reader.readAsDataURL(file);
}
// 清空 input 的值,这样即使用户连续选择相同的文件也能触发 change 事件
qrInput.value = '';
}
/**
* 创建并显示单个图片预览
* @param {string} src - 图片的 Data URL
* @param {string} name - 图片的文件名
*/
function createPreview(src, name) {
// 创建预览项的容器
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.title = name; // 鼠标悬停时显示文件名
// 创建图片元素
const img = document.createElement('img');
img.src = src;
img.alt = name;
// 创建移除按钮
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
removeBtn.textContent = '×';
removeBtn.title = '移除此图片';
// 为移除按钮添加点击事件
removeBtn.addEventListener('click', (e) => {
e.stopPropagation(); // 防止触发其他事件
previewItem.remove(); // 从 DOM 中移除预览项
});
// 将图片和移除按钮添加到预览项容器中
previewItem.appendChild(img);
previewItem.appendChild(removeBtn);
// 将预览项添加到主预览容器中
imagePreviewContainer.appendChild(previewItem);
}
// 为解码按钮添加点击事件
decodeBtn.addEventListener('click', () => {
// 清空之前的结果
resultText.value = '';
// 获取所有预览项中的图片元素,它们的顺序就是用户拖拽后的顺序
const images = imagePreviewContainer.querySelectorAll('.preview-item img');
if (images.length === 0) {
resultText.value = '请先上传至少一张二维码图片。';
return;
}
const decodedParts = [];
let completed = 0;
// 遍历所有图片进行解码
images.forEach((img, index) => {
const decodedText = decodeImage(img);
decodedParts[index] = decodedText;
completed++;
// 当所有图片都处理完毕后,拼接结果
if (completed === images.length) {
resultText.value = decodedParts.join('');
}
});
});
/**
* 解码单个图片元素
* @param {HTMLImageElement} img - 要解码的图片元素
* @returns {string} - 解码后的文本或者一个错误提示
*/
function decodeImage(img) {
// 创建一个临时的、离屏的 canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// --- 图片缩放逻辑 ---
// 定义一个用于处理的最大尺寸,过大的图片会导致 jsQR 性能下降或失败
const MAX_DIMENSION = 1000;
let { naturalWidth: width, naturalHeight: height } = img;
// 如果图片尺寸过大,则按比例缩小
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
if (width > height) {
height = Math.round(height * (MAX_DIMENSION / width));
width = MAX_DIMENSION;
} else {
width = Math.round(width * (MAX_DIMENSION / height));
height = MAX_DIMENSION;
}
}
// 设置 canvas 的尺寸为计算后的尺寸
canvas.width = width;
canvas.height = height;
// 将图片(可能会被缩放)绘制到 canvas 上
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 从 canvas 获取图像数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
try {
// 使用 jsQR 库解码
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code && code.data) {
// 解码成功,返回文本内容
return code.data;
} else {
// 未找到二维码
return `[图片 ${img.alt} 未能识别出二维码]`;
}
} catch (error) {
console.error(`解码图片 ${img.alt} 时发生错误:`, error);
return `[图片 ${img.alt} 解码失败]`;
}
}
// 为复制按钮添加点击事件
copyBtn.addEventListener('click', () => {
const textToCopy = resultText.value;
if (!textToCopy) {
return;
}
navigator.clipboard
.writeText(textToCopy)
.then(() => {
// 复制成功后的用户反馈
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制!';
copyBtn.disabled = true;
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.disabled = false;
}, 2000);
})
.catch((err) => {
console.error('复制文本失败:', err);
// 可以选择在这里给用户一个错误提示
alert('复制失败,您的浏览器可能不支持或未授权剪贴板操作。');
});
});
});

File diff suppressed because it is too large Load Diff

@ -0,0 +1,127 @@
// main.js
// 当整个页面的HTML内容加载完成后执行此函数
document.addEventListener('DOMContentLoaded', () => {
// 获取页面上的关键元素
const textInput = document.getElementById('text-input');
const generateBtn = document.getElementById('generate-btn');
const qrcodeContainer = document.getElementById('qrcode-container');
// 为“生成二维码”按钮添加点击事件监听器
generateBtn.addEventListener('click', () => {
// 获取输入框中的文本,并用 trim() 清除前后的空白字符
const text = textInput.value.trim();
// 在生成新的二维码之前,清空之前可能存在的旧二维码
qrcodeContainer.innerHTML = '';
// 如果文本为空,则不执行任何操作
if (!text) {
console.log('输入内容为空,已中止生成。');
return;
}
// --- 智能分割逻辑 ---
// 我们使用一个固定的字节数上限。这是二维码国标GB/T 18284-2000规定的
// 在版本40、纠错等级H最高的情况下最大可容纳的字节数。
const MAX_BYTES_PER_QR = 1273;
const textEncoder = new TextEncoder(); // 用于计算字符串的UTF-8字节长度
const chunks = [];
let currentPos = 0;
while (currentPos < text.length) {
// 使用二分查找来高效地找到当前位置后,能容纳在单个二维码中的最长子字符串
let low = currentPos;
let high = text.length;
let bestEnd = currentPos;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
// mid不能停在原点否则会死循环
if (mid === currentPos) {
low = mid + 1;
continue;
}
const substring = text.substring(currentPos, mid);
const byteLength = textEncoder.encode(substring).length;
if (byteLength <= MAX_BYTES_PER_QR) {
// 如果当前子字符串的字节长度在限制内,说明它是一个有效的块
// 我们记录下这个有效的终点,并尝试寻找更长的块
bestEnd = mid;
low = mid + 1;
} else {
// 如果超出了字节限制,则需要缩短子字符串的长度
high = mid - 1;
}
}
// 处理极端情况:如果单个字符的字节数就超过了上限,我们也只能把它单独作为一个块
if (bestEnd === currentPos && currentPos < text.length) {
bestEnd = currentPos + 1;
}
// 将找到的最佳块添加到数组中
chunks.push(text.substring(currentPos, bestEnd));
// 从新的位置继续寻找下一个块
currentPos = bestEnd;
}
const totalChunks = chunks.length;
console.log(`总字符数: ${text.length},通过智能分割,将生成 ${totalChunks} 个二维码。`);
// --- 循环生成二维码 ---
chunks.forEach((chunk, i) => {
// --- 创建包裹单个二维码及其标签的容器 ---
const itemDiv = document.createElement('div');
itemDiv.className = 'qrcode-item';
// --- 创建并添加序号标签 ---
const label = document.createElement('p');
label.className = 'qrcode-label';
label.textContent = totalChunks > 1 ? `二维码 ${i + 1}/${totalChunks}` : '二维码';
itemDiv.appendChild(label);
// --- 创建用于绘制二维码的 <canvas> 元素 ---
const canvas = document.createElement('canvas');
itemDiv.appendChild(canvas);
qrcodeContainer.appendChild(itemDiv);
// --- 使用 QRCode.toCanvas 方法将二维码绘制到指定的 canvas 上 ---
QRCode.toCanvas(
canvas,
chunk,
{
margin: 2, // 二维码边距
color: {
dark: '#000000', // 二维码深色部分
light: '#ffffff', // 二维码浅色部分
},
errorCorrectionLevel: 'H', // 纠错级别: 'L', 'M', 'Q', 'H'
},
function (error) {
if (error) {
console.error(`为第 ${i + 1} 块内容生成二维码时出错:`, error);
// 如果生成失败,在 canvas 上显示错误信息
const ctx = canvas.getContext('2d');
if (ctx) {
// 设置一个基础尺寸以防canvas没有宽高
canvas.width = 400;
canvas.height = 400;
ctx.font = '20px Arial';
ctx.fillStyle = 'red';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('二维码生成失败', canvas.width / 2, canvas.height / 2);
}
} else {
console.log(`已成功为第 ${i + 1} 块内容生成二维码。`);
}
}
);
});
});
});

File diff suppressed because one or more lines are too long

@ -1,75 +0,0 @@
// main.js
// 当整个页面的HTML内容加载完成后执行此函数
document.addEventListener('DOMContentLoaded', () => {
// 获取页面上的关键元素
const textInput = document.getElementById('text-input');
const generateBtn = document.getElementById('generate-btn');
const qrcodeContainer = document.getElementById('qrcode-container');
// 为“生成二维码”按钮添加点击事件监听器
generateBtn.addEventListener('click', () => {
// 获取输入框中的文本,并用 trim() 清除前后的空白字符
const text = textInput.value.trim();
// 在生成新的二维码之前,清空之前可能存在的旧二维码
qrcodeContainer.innerHTML = '';
// 如果文本为空,则不执行任何操作
if (!text) {
console.log('输入内容为空,已中止生成。');
return;
}
// 定义每个二维码的最大字符数
const chunkSize = 1000;
// 计算需要生成多少个二维码
const totalChunks = Math.ceil(text.length / chunkSize);
console.log(`总字符数: ${text.length},将生成 ${totalChunks} 个二维码。`);
// 循环处理每一块文本
for (let i = 0; i < totalChunks; i++) {
// 计算当前块的起始和结束位置
const start = i * chunkSize;
const end = start + chunkSize;
// 从总文本中截取当前块的文本
const chunk = text.substring(start, end);
// --- 创建包裹单个二维码及其标签的容器 ---
const itemDiv = document.createElement('div');
itemDiv.className = 'qrcode-item';
// --- 创建并添加序号标签 ---
const label = document.createElement('p');
label.className = 'qrcode-label';
// 如果总数大于1则显示 "二维码 x/y",否则只显示 "二维码"
label.textContent = totalChunks > 1 ? `二维码 ${i + 1}/${totalChunks}` : '二维码';
itemDiv.appendChild(label);
// --- 创建用于生成二维码的占位符元素 ---
const qrcodeDiv = document.createElement('div');
// 给这个div一个唯一的ID虽然qrcodejs也可以直接接收元素对象
qrcodeDiv.id = `qrcode-${i}`;
itemDiv.appendChild(qrcodeDiv);
// --- 将完整的二维码项(标签+占位符)添加到主容器中 ---
qrcodeContainer.appendChild(itemDiv);
// --- 使用 qrcode.js 库生成二维码 ---
// 实例化 QRCode 对象
// 第一个参数是二维码要渲染到的DOM元素
// 第二个参数是配置对象
new QRCode(qrcodeDiv, {
text: chunk, // 当前块的文本内容
width: 400, // 二维码宽度
height: 400, // 二维码高度
colorDark: '#000000', // 二维码颜色
colorLight: '#ffffff', // 二维码背景色
correctLevel: QRCode.CorrectLevel.H, // 纠错级别H是最高级
});
console.log(`已为第 ${i + 1} 块内容生成二维码。`);
}
});
});

File diff suppressed because one or more lines are too long

@ -2,112 +2,117 @@
/* 全局样式和基础布局 */ /* 全局样式和基础布局 */
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #f4f5f7; background-color: #f4f5f7;
color: #172b4d; color: #172b4d;
margin: 0; margin: 0;
padding: 20px; padding: 20px;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.container { .container {
width: 100%; width: 100%;
max-width: 800px; max-width: 1200px;
background-color: #ffffff; background-color: #ffffff;
padding: 24px 48px; padding: 24px 48px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
box-sizing: border-box; box-sizing: border-box;
} }
h1 { h1 {
color: #091e42; color: #091e42;
text-align: center; text-align: center;
} }
p { p {
text-align: center; text-align: center;
color: #5e6c84; color: #5e6c84;
margin-bottom: 24px; margin-bottom: 24px;
} }
/* 文本输入框样式 */ /* 文本输入框样式 */
textarea#text-input { textarea#text-input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 2px solid #dfe1e6; border: 2px solid #dfe1e6;
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
box-sizing: border-box; box-sizing: border-box;
resize: vertical; /* 允许用户垂直调整大小 */ resize: vertical; /* 允许用户垂直调整大小 */
transition: border-color 0.2s, box-shadow 0.2s; transition:
border-color 0.2s,
box-shadow 0.2s;
} }
textarea#text-input:focus { textarea#text-input:focus {
outline: none; outline: none;
border-color: #4c9aff; border-color: #4c9aff;
box-shadow: 0 0 0 2px rgba(76, 154, 255, 0.3); box-shadow: 0 0 0 2px rgba(76, 154, 255, 0.3);
} }
/* 按钮样式 */ /* 按钮样式 */
button#generate-btn { button#generate-btn {
display: block; display: block;
width: 100%; width: 100%;
padding: 12px; padding: 12px;
margin: 20px 0; margin: 20px 0;
background-color: #0052cc; background-color: #0052cc;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
button#generate-btn:hover { button#generate-btn:hover {
background-color: #0065ff; background-color: #0065ff;
} }
button#generate-btn:active { button#generate-btn:active {
background-color: #0747a6; background-color: #0747a6;
} }
/* 二维码容器和项目样式 */ /* 二维码容器和项目样式 */
#qrcode-container { #qrcode-container {
margin-top: 24px; margin-top: 24px;
display: flex; display: flex;
flex-direction: column; /* 垂直排列 */ flex-direction: column; /* 垂直排列 */
align-items: center; /* 居中对齐 */ align-items: stretch; /* 让子项目qrcode-item在交叉轴上填满容器 */
gap: 30px; /* 二维码之间的间距 */ gap: 30px; /* 二维码之间的间距 */
} }
.qrcode-item { .qrcode-item {
display: flex; width: 100%; /* 明确宽度为100% */
flex-direction: column; box-sizing: border-box;
align-items: center; display: flex;
padding: 15px; flex-direction: column;
background-color: #fafbfc; align-items: center; /* 容器内的内容标签和canvas居中 */
border: 1px solid #dfe1e6; padding: 15px;
border-radius: 4px; background-color: #fafbfc;
border: 1px solid #dfe1e6;
border-radius: 4px;
} }
.qrcode-label { .qrcode-label {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
color: #5e6c84; color: #5e6c84;
margin-bottom: 10px; margin-bottom: 10px;
} }
/* /*
CSS canvas qrcode-item
auto
*/ */
.qrcode-item img { .qrcode-item canvas {
max-width: 400px; width: 100%;
height: auto; height: auto;
background-color: white; display: block; /* 移除 canvas 作为内联元素可能产生的额外空间 */
padding: 10px; background-color: white;
border: 1px solid #ccc; padding: 10px;
border: 1px solid #ccc;
box-sizing: border-box;
} }

@ -0,0 +1,361 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>DOM元素框选工具</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
#container {
position: relative;
width: 100%;
height: 80vh;
border: 2px solid #333;
background-color: white;
overflow: hidden;
}
.selectable-element {
position: absolute;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.selectable-element:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: scale(1.02);
}
.selectable-element.selected {
border: 2px solid #007bff;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
z-index: 10;
}
#selection-box {
position: absolute;
border: 2px dashed #007bff;
background-color: rgba(0, 123, 255, 0.1);
pointer-events: none;
display: none;
}
#result {
margin-top: 20px;
padding: 15px;
background-color: #e9ecef;
border-radius: 5px;
min-height: 100px;
}
button {
margin: 5px;
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
h1 {
color: #333;
}
</style>
</head>
<body>
<h1>DOM元素框选工具</h1>
<div id="container">
<!-- 可选择的元素 -->
<div
class="selectable-element"
style="width: 100px; height: 60px; top: 50px; left: 50px; background-color: #ff6b6b"
>
图片1
</div>
<div
class="selectable-element"
style="width: 120px; height: 80px; top: 150px; left: 200px; background-color: #4ecdc4"
>
按钮1
</div>
<div
class="selectable-element"
style="width: 150px; height: 70px; top: 300px; left: 100px; background-color: #45b7d1"
>
文本框
</div>
<div
class="selectable-element"
style="width: 80px; height: 80px; top: 200px; left: 400px; background-color: #96ceb4"
>
图片2
</div>
<div
class="selectable-element"
style="width: 100px; height: 50px; top: 100px; left: 500px; background-color: #feca57"
>
按钮2
</div>
<div
class="selectable-element"
style="width: 130px; height: 90px; top: 350px; left: 400px; background-color: #ff9ff3"
>
文本区域
</div>
<div
class="selectable-element"
style="width: 90px; height: 90px; top: 50px; left: 600px; background-color: #54a0ff"
>
图标
</div>
<div
class="selectable-element"
style="width: 110px; height: 60px; top: 250px; left: 650px; background-color: #5f27cd"
>
标签
</div>
<div
class="selectable-element"
style="width: 140px; height: 80px; top: 150px; left: 750px; background-color: #00d2d3"
>
输入框
</div>
<div
class="selectable-element"
style="width: 100px; height: 100px; top: 300px; left: 700px; background-color: #ff6348"
>
按钮3
</div>
<!-- 框选框 -->
<div id="selection-box"></div>
</div>
<div>
<button onclick="clearSelection()">清除选择</button>
<button onclick="getSelectedElements()">获取选中元素</button>
</div>
<div id="result">
<h3>选中元素列表:</h3>
<p id="selected-list">暂无选中元素</p>
</div>
<script>
let isSelecting = false;
let startX, startY;
let selectionBox = document.getElementById('selection-box');
let container = document.getElementById('container');
let elements = document.querySelectorAll('.selectable-element');
// 鼠标按下事件
container.addEventListener('mousedown', function (e) {
// 如果点击的是元素本身,则不进行框选
if (e.target.classList.contains('selectable-element')) {
return;
}
isSelecting = true;
// 获取相对于容器的坐标
const rect = container.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
// 设置框选框的位置和大小
selectionBox.style.left = startX + 'px';
selectionBox.style.top = startY + 'px';
selectionBox.style.width = '0px';
selectionBox.style.height = '0px';
selectionBox.style.display = 'block'; // 防止拖拽时选择文字
e.preventDefault();
});
// 鼠标移动事件
container.addEventListener('mousemove', function (e) {
if (!isSelecting) return;
const rect = container.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
// 计算框选框的宽度和高度
const width = Math.abs(currentX - startX);
const height = Math.abs(currentY - startY);
// 确定框选框的左上角位置
const left = Math.min(startX, currentX);
const top = Math.min(startY, currentY);
// 更新框选框的样式
selectionBox.style.left = left + 'px';
selectionBox.style.top = top + 'px';
selectionBox.style.width = width + 'px';
selectionBox.style.height = height + 'px';
});
// 鼠标释放事件
container.addEventListener('mouseup', function (e) {
if (!isSelecting) return;
isSelecting = false;
// 获取框选范围(在隐藏之前)
const containerRect = container.getBoundingClientRect();
const selectionLeft = parseFloat(selectionBox.style.left);
const selectionTop = parseFloat(selectionBox.style.top);
const selectionWidth = parseFloat(selectionBox.style.width);
const selectionHeight = parseFloat(selectionBox.style.height);
// 修复确保最小尺寸为1像素避免0值问题
const finalWidth = Math.max(selectionWidth, 1);
const finalHeight = Math.max(selectionHeight, 1);
const finalLeft = selectionLeft;
const finalTop = selectionTop;
console.log('Selection box info:', {
left: finalLeft,
top: finalTop,
width: finalWidth,
height: finalHeight,
containerRect: containerRect,
});
// 隐藏框选框
selectionBox.style.display = 'none';
// 检查哪些元素在框选范围内
checkSelection(finalLeft, finalTop, finalWidth, finalHeight);
});
// 检查元素是否在框选范围内
function checkSelection(left, top, width, height) {
console.log('Checking selection:', { left, top, width, height });
const selectedElements = [];
elements.forEach((element) => {
const elementRect = element.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// 转换为相对于容器的坐标
const elementLeft = elementRect.left - containerRect.left;
const elementTop = elementRect.top - containerRect.top;
const elementWidth = elementRect.width;
const elementHeight = elementRect.height;
console.log('Element info:', {
text: element.textContent,
left: elementLeft,
top: elementTop,
width: elementWidth,
height: elementHeight,
});
// 矩形相交检测算法
// 两个矩形相交的条件是在x轴和y轴上都有重叠
const xOverlap = elementLeft < left + width && elementLeft + elementWidth > left;
const yOverlap = elementTop < top + height && elementTop + elementHeight > top;
console.log('Overlap check:', {
xOverlap,
yOverlap,
condition: xOverlap && yOverlap,
});
if (xOverlap && yOverlap) {
selectedElements.push(element);
}
});
console.log('Selected elements count:', selectedElements.length);
// 标记选中的元素
markSelectedElements(selectedElements);
// 显示结果
displayResults(selectedElements);
}
// 标记选中的元素
function markSelectedElements(elements) {
// 先清除所有已选中的标记
document.querySelectorAll('.selectable-element.selected').forEach((el) => {
el.classList.remove('selected');
});
// 给新选中的元素添加标记
elements.forEach((element) => {
element.classList.add('selected');
});
}
// 显示结果
function displayResults(elements) {
const resultElement = document.getElementById('selected-list');
if (elements.length === 0) {
resultElement.innerHTML = '没有选中任何元素';
return;
}
let html = '<ul>';
elements.forEach((element, index) => {
html += `<li>${index + 1}. ${element.textContent || '未知元素'}</li>`;
});
html += '</ul>';
resultElement.innerHTML = html;
}
// 清除选择
function clearSelection() {
document.querySelectorAll('.selectable-element.selected').forEach((el) => {
el.classList.remove('selected');
});
document.getElementById('selected-list').innerHTML = '暂无选中元素';
}
// 获取选中元素
function getSelectedElements() {
const selectedElements = document.querySelectorAll('.selectable-element.selected');
const elementsArray = Array.from(selectedElements);
displayResults(elementsArray);
}
// 添加鼠标离开容器时的处理,防止拖拽时鼠标移出容器导致的问题
document.addEventListener('mouseleave', function (e) {
if (isSelecting && e.target === container) {
isSelecting = false;
selectionBox.style.display = 'none';
}
});
// 添加document mouseup事件防止鼠标在容器外释放时的问题
document.addEventListener('mouseup', function (e) {
if (isSelecting) {
isSelecting = false;
selectionBox.style.display = 'none';
}
});
</script>
</body>
</html>
Loading…
Cancel
Save