Files
dsProject/dsLightRag/XingJun/move.html
2025-09-09 16:00:33 +08:00

439 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>箭头动画</title>
<style>
body { margin: 0; padding: 0; height: 100vh; display: flex; overflow: hidden; }
.background-container { position: relative; flex: 1; height: 100vh; background-image: url('background.png'); background-position: center; background-repeat: no-repeat; background-size: contain; z-index: 1; }
.control-panel { width: 200px; background-color: #fff; border-left: 1px solid #ccc; padding: 20px; height: 100vh; box-sizing: border-box; z-index: 2; overflow-y: auto; }
.control-section { margin-bottom: 30px; }
.control-section h3 { margin-top: 0; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.image-selector { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px; }
.image-btn { width: 70px; height: 70px; border: 2px solid #ddd; border-radius: 4px; cursor: pointer; background-size: contain; background-repeat: no-repeat; background-position: center; transition: all 0.2s; }
.image-btn:hover { border-color: #4CAF50; transform: scale(1.05); }
.image-btn:active { transform: scale(0.98); }
.arrow-container { position: absolute; pointer-events: auto; }
.arrow-image { width: 155px; height: 173px; background-size: contain; background-repeat: no-repeat; cursor: move; }
.save-btn { width: 100%; padding: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-top: 20px; }
.save-btn:hover { background-color: #45a049; }
.arrow-text { position: absolute; top: '100%'; left: '50%'; transform: translateX(-50%); margin-top: '-5px'; font-weight: bold; color: red; font-size: 16px; white-space: nowrap; text-align: center; width: 100%; background-color: transparent; padding: 3px 8px; border-radius: 4px; }
#contextMenu { position: absolute; display: none; background: white; border: 1px solid #ccc; padding: 5px; z-index: 9999; }
#deleteArrow { padding: 3px 10px; cursor: pointer; }
.menu-separator { height: 1px; background-color: #ccc; margin: 5px 0; }
.menu-item { padding: 3px 10px; cursor: pointer; }
.menu-item:hover { background-color: #f0f0f0; }
.check-mark { display: inline-block; width: 16px; text-align: center; }
.center-dot { position: absolute; width: 8px; height: 8px; background: red; border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; z-index: 5; }
</style>
</head>
<body>
<div class="background-container"></div>
<div class="control-panel">
<div class="control-section">
<h3>增加箭头</h3>
<div class="image-selector">
<div class="image-btn" data-image="./1.png" style="background-image: url('./1.png');"></div>
<div class="image-btn" data-image="./2.png" style="background-image: url('./2.png');"></div>
<div class="image-btn" data-image="./3.png" style="background-image: url('./3.png');"></div>
<div class="image-btn" data-image="./4.png" style="background-image: url('./4.png');"></div>
<div class="image-btn" data-image="./5.png" style="background-image: url('./5.png');"></div>
<div class="image-btn" data-image="./6.png" style="background-image: url('./6.png');"></div>
</div>
<!-- 移除保存位置按钮 -->
<button class="save-btn" id="markCenterBtn" style="margin-top: 10px;">标记中心点</button>
<button class="save-btn" id="startAnimationBtn" style="margin-top: 10px;">开始</button>
<button class="save-btn" id="resetBtn" style="margin-top: 10px;">重置</button>
</div>
</div>
<div id="contextMenu">
<div id="deleteArrow">删除箭头</div>
<div class="menu-separator"></div>
<div class="menu-item" data-point="top-left"><span class="check-mark"></span>左上角</div>
<div class="menu-item" data-point="top-right"><span class="check-mark"></span>右上角</div>
<div class="menu-item" data-point="bottom-left"><span class="check-mark"></span>左下角</div>
<div class="menu-item" data-point="bottom-right"><span class="check-mark"></span>右下角</div>
</div>
<script>
// 修复核心整理所有JS代码到单个script标签内
document.addEventListener('DOMContentLoaded', function() {
const backgroundContainer = document.querySelector('.background-container');
const imageButtons = document.querySelectorAll('.image-btn');
const saveBtn = document.getElementById('savePositionsBtn');
const markCenterBtn = document.getElementById('markCenterBtn');
const startAnimationBtn = document.getElementById('startAnimationBtn');
const resetBtn = document.getElementById('resetBtn');
const contextMenu = document.getElementById('contextMenu');
const deleteArrow = document.getElementById('deleteArrow');
let selectedArrow = null;
let centerDot = null;
// 添加重置按钮事件监听器
resetBtn.addEventListener('click', function() {
location.reload();
});
// 添加开始按钮事件监听器
startAnimationBtn.addEventListener('click', startAnimation);
// 动画控制函数 - 确保在DOMContentLoaded回调内部定义
function startAnimation() {
// 检查是否已设置中心点
if (!centerDot) {
alert('请先确定中心点');
return;
}
// 获取中心点坐标修复使用getBoundingClientRect确保坐标正确
const centerRect = centerDot.getBoundingClientRect();
const containerRect = backgroundContainer.getBoundingClientRect();
const centerX = centerRect.left - containerRect.left;
const centerY = centerRect.top - containerRect.top;
// 获取所有箭头
const arrows = document.querySelectorAll('.arrow-container');
if (arrows.length === 0) {
alert('没有可移动的箭头');
return;
}
// 为每个箭头添加动画
arrows.forEach(arrow => {
animateArrow(arrow, centerX, centerY);
});
}
// 单个箭头动画函数
function animateArrow(arrow, targetX, targetY) {
// 获取箭头图片尺寸固定为155px×173px
const arrowWidth = 155;
const arrowHeight = 173;
// 计算中心点偏移量(宽度和高度的一半)
const centerOffsetX = arrowWidth / 2;
const centerOffsetY = arrowHeight / 2;
// 定义停止距离(箭头中心点与目标中心点的距离)
const stopDistance = 50;
// 获取箭头当前位置(相对于背景容器)
const rect = arrow.getBoundingClientRect();
const containerRect = backgroundContainer.getBoundingClientRect();
// 当前位置需要加上偏移量,获取实际中心点位置
let currentX = rect.left - containerRect.left + centerOffsetX;
let currentY = rect.top - containerRect.top + centerOffsetY;
// 添加随机偏移避免重叠
const offsetRange = 20;
const randomOffsetX = (Math.random() - 0.5) * offsetRange;
const randomOffsetY = (Math.random() - 0.5) * offsetRange;
const finalTargetX = targetX + randomOffsetX;
const finalTargetY = targetY + randomOffsetY;
// 计算方向向量和距离
const dxTotal = finalTargetX - currentX;
const dyTotal = finalTargetY - currentY;
const distanceTotal = Math.sqrt(dxTotal * dxTotal + dyTotal * dyTotal);
// 计算调整后的目标位置保持stopDistance距离
let adjustedTargetX, adjustedTargetY;
if (distanceTotal > stopDistance) {
// 如果距离大于停止距离,计算新目标位置
const ratio = (distanceTotal - stopDistance) / distanceTotal;
adjustedTargetX = currentX + dxTotal * ratio;
adjustedTargetY = currentY + dyTotal * ratio;
} else {
// 如果已在停止距离内,保持当前位置
adjustedTargetX = currentX;
adjustedTargetY = currentY;
}
function updatePosition() {
const dx = adjustedTargetX - currentX;
const dy = adjustedTargetY - currentY;
const distance = Math.sqrt(dx * dx + dy * dy);
// 当距离小于2px时停止动画确保到达调整后的目标位置
if (distance < 2) {
arrow.style.left = (adjustedTargetX - centerOffsetX) + 'px';
arrow.style.top = (adjustedTargetY - centerOffsetY) + 'px';
return;
}
const step = Math.max(distance * 0.05, 1);
const moveX = (dx / distance) * step;
const moveY = (dy / distance) * step;
currentX += moveX;
currentY += moveY;
arrow.style.left = (currentX - centerOffsetX) + 'px';
arrow.style.top = (currentY - centerOffsetY) + 'px';
requestAnimationFrame(updatePosition);
}
updatePosition();
}
// 加载保存的元素
loadAllElements();
// 绑定箭头按钮事件
imageButtons.forEach(button => {
button.addEventListener('click', () => {
const imagePath = button.getAttribute('data-image');
addImageToContainer(imagePath);
});
});
// 保存按钮事件
// 删除保存按钮事件监听器
// saveBtn.addEventListener('click', saveAllElements);
// 右键菜单事件
deleteArrow.addEventListener('click', function() {
if (selectedArrow) {
selectedArrow.remove();
saveAllElements();
contextMenu.style.display = 'none';
selectedArrow = null;
}
});
// 为匹配角菜单项添加事件监听器
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', function() {
if (selectedArrow) {
const referencePoint = this.getAttribute('data-point');
selectedArrow.dataset.referencePoint = referencePoint;
saveAllElements(); // 保存更改
contextMenu.style.display = 'none';
selectedArrow = null;
}
});
});
// 加载保存的元素
// 点击空白处关闭菜单
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
selectedArrow = null;
});
// 标记中心点功能
markCenterBtn.addEventListener('click', function() {
// 移除旧点
if (centerDot) {
centerDot.remove();
centerDot = null;
}
// 创建新点
function handleMapClick(e) {
const rect = backgroundContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
centerDot = document.createElement('div');
centerDot.className = 'center-dot';
centerDot.style.left = x + 'px';
centerDot.style.top = y + 'px';
backgroundContainer.appendChild(centerDot);
backgroundContainer.removeEventListener('click', handleMapClick);
saveAllElements(); // 中心点标记后自动保存
}
backgroundContainer.addEventListener('click', handleMapClick);
});
// 添加箭头函数
function addImageToContainer(imagePath, position = null, textContent = '') {
const container = document.createElement('div');
container.className = 'arrow-container';
container.dataset.imagePath = imagePath;
const newImage = document.createElement('div');
newImage.className = 'arrow-image';
newImage.style.backgroundImage = `url('${imagePath}')`;
container.appendChild(newImage);
// 添加文字元素
if (textContent) {
const textElement = document.createElement('div');
textElement.className = 'arrow-text';
textElement.textContent = textContent;
container.dataset.text = textContent;
container.appendChild(textElement);
}
// 设置位置 - 使用中心点作为参照
const arrowWidth = 155;
const arrowHeight = 173;
const centerOffsetX = arrowWidth / 2;
const centerOffsetY = arrowHeight / 2;
if (position) {
// 修复:直接使用传递的左上角位置,不再减去偏移量
container.style.left = parseFloat(position.left) + 'px';
container.style.top = parseFloat(position.top) + 'px';
} else {
// 随机位置也使用中心点作为参照
const x = Math.random() * (backgroundContainer.offsetWidth - arrowWidth) + centerOffsetX;
const y = Math.random() * (backgroundContainer.offsetHeight - arrowHeight) + centerOffsetY;
container.style.left = (x - centerOffsetX) + 'px';
container.style.top = (y - centerOffsetY) + 'px';
}
// 双击编辑文字
container.addEventListener('dblclick', (e) => {
e.stopPropagation();
const currentText = container.dataset.text || '';
const textContent = prompt('请输入文字:', currentText);
if (textContent !== null) {
if (textContent.trim() === '') {
container.querySelector('.arrow-text')?.remove();
delete container.dataset.text;
} else {
let textElement = container.querySelector('.arrow-text');
if (!textElement) {
textElement = document.createElement('div');
textElement.className = 'arrow-text';
container.appendChild(textElement);
}
textElement.textContent = textContent;
container.dataset.text = textContent;
saveAllElements(); // 文字编辑后自动保存
}
}
});
// 右键菜单支持
container.addEventListener('contextmenu', function(e) {
e.preventDefault();
selectedArrow = this;
contextMenu.style.left = `${e.clientX}px`;
contextMenu.style.top = `${e.clientY}px`;
contextMenu.style.display = 'block';
// 显示当前选中的匹配角对号
const currentPoint = selectedArrow.dataset.referencePoint || 'top-left';
document.querySelectorAll('.menu-item').forEach(item => {
const checkMark = item.querySelector('.check-mark');
if (item.dataset.point === currentPoint) {
checkMark.textContent = '✓';
} else {
checkMark.textContent = '';
}
});
});
// 拖拽功能
makeElementDraggable(container);
backgroundContainer.appendChild(container);
saveAllElements(); // 添加后自动保存
return container;
}
// 拖拽实现
function makeElementDraggable(element) {
let isDragging = false, offsetX, offsetY;
element.addEventListener('mousedown', (e) => {
if (e.button === 2) return; // 忽略右键
isDragging = true;
const rect = element.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
element.style.zIndex = '10';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
element.style.left = `${x}px`;
element.style.top = `${y}px`;
// 实时保存并控制台输出
saveAllElements();
console.log(`箭头移动并保存: 位置(${x}, ${y})`);
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
element.style.zIndex = '2';
saveAllElements(); // 拖拽结束后自动保存
}
});
}
// 保存/加载功能
function saveAllElements() {
const elements = [];
const arrowWidth = 155;
const arrowHeight = 173;
const centerOffsetX = arrowWidth / 2;
const centerOffsetY = arrowHeight / 2;
document.querySelectorAll('.arrow-container').forEach(arrow => {
// 保存中心点坐标而非左上角坐标
const left = parseFloat(arrow.style.left) || 0;
const top = parseFloat(arrow.style.top) || 0;
elements.push({
type: 'arrow',
imagePath: arrow.dataset.imagePath,
left: left + centerOffsetX,
top: top + centerOffsetY,
text: arrow.dataset.text || '',
referencePoint: arrow.dataset.referencePoint || 'top-left' // 默认左上角
});
});
// 保存中心点
if (centerDot) {
elements.push({
type: 'center',
left: centerDot.style.left,
top: centerDot.style.top
});
}
localStorage.setItem('savedElements', JSON.stringify(elements));
// 控制台输出详细保存信息
console.log(`[${new Date().toLocaleTimeString()}] 自动保存完成: 共${elements.length}个元素`);
console.log('保存内容:', elements);
}
function loadAllElements() {
const savedElements = JSON.parse(localStorage.getItem('savedElements') || '[]');
const arrowWidth = 155;
const arrowHeight = 173;
const centerOffsetX = arrowWidth / 2;
const centerOffsetY = arrowHeight / 2;
savedElements.forEach(item => {
if (item.type === 'arrow') {
// 加载时使用中心点坐标减去偏移量得到左上角位置
addImageToContainer(item.imagePath, {
left: item.left - centerOffsetX,
top: item.top - centerOffsetY
}, item.text);
} else if (item.type === 'center') {
// 创建中心点
centerDot = document.createElement('div');
centerDot.className = 'center-dot';
centerDot.style.left = item.left;
centerDot.style.top = item.top;
backgroundContainer.appendChild(centerDot);
}
});
}
}); // DOMContentLoaded回调结束
// 确保这里没有残留的startAnimation函数定义
</script>
</body>
</html>