console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="theme-color" content="#1b3a57">
<title>我的专属图片桌宠</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
width: 100%; height: 100%; overflow: hidden;
background: radial-gradient(circle at 50% 38%, #1b3a57 0%, #0a1622 72%);
touch-action: none; user-select: none; -webkit-user-select: none;
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
}
/* 上传界面的样式 */
#upload-ui {
position: fixed; inset: 0; display: flex; flex-direction: column;
align-items: center; justify-content: center; color: #fff;
background: rgba(10, 22, 34, 0.9); z-index: 100;
transition: opacity 0.3s;
}
#upload-ui.hide { opacity: 0; pointer-events: none; }
#upload-ui h2 { font-size: 22px; margin-bottom: 20px; color: #8FE3FF; }
.upload-btn {
padding: 14px 30px; font-size: 16px; font-weight: bold; color: #05101a;
background: linear-gradient(135deg, #8FE3FF, #2AA0DC);
border: none; border-radius: 30px; box-shadow: 0 8px 20px rgba(42, 160, 220, 0.4);
cursor: pointer; transition: transform 0.1s; margin-bottom: 20px;
}
.upload-btn:active { transform: scale(0.95); }
#file-input { display: none; }
.tips { font-size: 13px; color: #9cb8cc; text-align: center; line-height: 1.6; }
/* 桌宠容器样式 */
#pet-container {
position: absolute; left: 50%; top: 50%;
transform: translate(-50%, -50%);
display: none; /* 默认隐藏,上传后显示 */
cursor: grab;
touch-action: none;
filter: drop-shadow(0 10px 15px rgba(0,0,0,0.5)); /* 给图片加点立体阴影 */
}
#pet-container:active { cursor: grabbing; }
#pet-img {
width: 280px; /* 手机上默认大小,可调整 */
max-width: 80vw;
pointer-events: none;
}
/* 气泡样式 */
#bubble {
position: absolute; left: 50%; top: -30px;
transform: translate(-50%, -100%) scale(0.7);
background: #fff; color: #333;
padding: 10px 18px; border-radius: 20px;
font-size: 16px; font-weight: bold;
white-space: nowrap; opacity: 0; pointer-events: none;
transition: transform 0.2s cubic-bezier(0.3, 1.6, 0.6, 1), opacity 0.2s;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
#bubble.show { opacity: 1; transform: translate(-50%, -100%) scale(1); }
#bubble::after {
content: ""; position: absolute; left: 50%; bottom: -8px;
transform: translateX(-50%);
border: 9px solid transparent; border-top-color: #fff; border-bottom: 0;
}
</style>
</head>
<body>
<!-- 上传界面 -->
<div id="upload-ui">
<h2>召唤你的专属桌宠</h2>
<button class="upload-btn" id="upload-btn">选择图片</button>
<input type="file" id="file-input" accept="image/*">
<p class="tips">建议选择背景透明的 PNG 图片<br>(如果图片有白底,可以在下一步选去除)</p>
</div>
<!-- 桌宠展示区 -->
<div id="pet-container">
<div id="bubble">你看,又急</div>
<img id="pet-img" src="" alt="pet">
</div>
<script>
(function(){
const uploadUI = document.getElementById('upload-ui');
const uploadBtn = document.getElementById('upload-btn');
const fileInput = document.getElementById('file-input');
const petContainer = document.getElementById('pet-container');
const petImg = document.getElementById('pet-img');
const bubble = document.getElementById('bubble');
const LINES = [
'你看,又急',
'别戳啦!',
'今天也要好好工作哦~',
'要摸摸尾巴吗?',
'哼,才不是特意来陪你的'
];
// 拖拽状态
let isDragging = false;
let startX = 0, startY = 0;
let initialX = 0, initialY = 0;
let moveThreshold = 5; // 移动超过5像素算拖拽,否则算点击
let hasMoved = false;
// 初始位置居中 (绝对定位,但通过 left/top 改变)
let currentX = window.innerWidth / 2;
let currentY = window.innerHeight / 2;
let isCentered = true;
uploadBtn.addEventListener('click', () => fileInput.click());
// 处理上传的图片
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
// 处理图片(去白底)
removeWhiteBackground(img);
// 切换界面
uploadUI.classList.add('hide');
petContainer.style.display = 'block';
// 居中显示
currentX = window.innerWidth / 2;
currentY = window.innerHeight / 2;
petContainer.style.left = currentX + 'px';
petContainer.style.top = currentY + 'px';
petContainer.style.transform = 'translate(-50%, -50%)';
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
// 核心魔法:去除纯白背景
function removeWhiteBackground(img) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 遍历所有像素
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// 如果RGB值都很高(接近纯白),则把透明度设为0
if (r > 240 && g > 240 && b > 240) {
data[i + 3] = 0;
}
}
ctx.putImageData(imageData, 0, 0);
petImg.src = canvas.toDataURL('image/png');
}
// --- 互动逻辑 ---
function say(text) {
bubble.textContent = text;
bubble.classList.add('show');
clearTimeout(window.sayTimer);
window.sayTimer = setTimeout(() => bubble.classList.remove('show'), 2000);
}
// 按下
function dragStart(e) {
if (e.target === uploadBtn) return;
isDragging = true;
hasMoved = false;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
startX = clientX;
startY = clientY;
const rect = petContainer.getBoundingClientRect();
// 记录鼠标按下时,点击位置相对于图片中心的偏移量
initialX = startX - (rect.left + rect.width / 2);
initialY = startY - (rect.top + rect.height / 2);
petContainer.style.transform = 'translate(-50%, -50%) scale(1.05)'; // 抓取时稍微放大
document.addEventListener('mousemove', dragMove, { passive: false });
document.addEventListener('mouseup', dragEnd);
document.addEventListener('touchmove', dragMove, { passive: false });
document.addEventListener('touchend', dragEnd);
}
// 移动
function dragMove(e) {
if (!isDragging) return;
if (e.cancelable) e.preventDefault();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
// 计算是否移动了足够的距离(为了防止长按轻微的抖动被判定为拖拽)
const dx = clientX - startX;
const dy = clientY - startY;
if (Math.abs(dx) > moveThreshold || Math.abs(dy) > moveThreshold) {
hasMoved = true;
}
if (hasMoved) {
currentX = clientX - initialX;
currentY = clientY - initialY;
petContainer.style.left = currentX + 'px';
petContainer.style.top = currentY + 'px';
bubble.classList.remove('show'); // 拖拽时隐藏气泡
}
}
// 松开
function dragEnd() {
if (!isDragging) return;
isDragging = false;
petContainer.style.transform = 'translate(-50%, -50%) scale(1)';
// 如果没有移动,说明是点击,触发对话
if (!hasMoved) {
const line = LINES[Math.floor(Math.random() * LINES.length)];
say(line);
// 点击跳动动画
petContainer.animate([
{ transform: 'translate(-50%, -50%) scale(1)' },
{ transform: 'translate(-50%, -55%) scale(1.1)' },
{ transform: 'translate(-50%, -50%) scale(1)' }
], { duration: 300, easing: 'ease-out' });
}
document.removeEventListener('mousemove', dragMove);
document.removeEventListener('mouseup', dragEnd);
document.removeEventListener('touchmove', dragMove);
document.removeEventListener('touchend', dragEnd);
}
petContainer.addEventListener('mousedown', dragStart);
petContainer.addEventListener('touchstart', dragStart, { passive: false });
})();
</script>
</body>
</html>