基于 杀马特风格特效 2 代网页源码分享 得来,实现了对评论区用户的 juejue 调教,并支持图片下载。
触发方式:评论区 hover 对方头像或昵称 2 秒
二次 juejue 保护,等消肿了再来吧~
适配 xiuno 程序:
头部添加定义
<script>
window.visitUser = {
uid: '<?php echo htmlspecialchars($user['uid'], ENT_QUOTES);?>',
username: '<?php echo htmlspecialchars($user['username'], ENT_QUOTES);?>',
avatarUrl: '<?php echo htmlspecialchars($user['avatar_url'], ENT_QUOTES);?>'
};
</script>
JavaScript
修改评论文件 post_list.inc.htm ,在输出评论者头像与昵称的 <a></a > 标签中添加
data-owner-uid="<?php echo htmlspecialchars($_post['uid'], ENT_QUOTES);?>"
data-owner-name="<?php echo htmlspecialchars($_post['username'], ENT_QUOTES);?>"
data-owner-avatar="<?php echo htmlspecialchars($_post['user_avatar_url'], ENT_QUOTES);?>"
Markup
最后,在帖子页底部添加
<script type='text/javascript'>
document.addEventListener('DOMContentLoaded', function() {
const visitUser = window.visitUser || {};
if (!visitUser.uid || visitUser.uid === '0' || visitUser.uid === '') {
return;
}
const JUE_COOLDOWN = 10 * 60 * 1000;
const EFFECT_DURATION = 10 * 1000;
const TIP_DURATION = 2000;
const HOVER_DELAY = 2000;
const FRAME_DELAY = 80;
let hoverTimer = null;
let currentFloorData = null;
let smartbgm = null;
let effectTimer = null;
let animationFrameId = null;
let currentFrame = 0;
let lastFrameTime = 0;
let canvas = null;
let ctx = null;
let customMenu = null;
let isMenuShow = false;
const images = {
bg: [null, null, null],
visitAvatar: null,
ownerAvatar: null
};
const visitAvatarPositions = {
1: { x: 106 + 54, y: -7 + 54, size: 108 },
2: { x: 99 + 54, y: 3 + 54, size: 108 },
3: { x: 118 + 54, y: -11 + 54, size: 108 }
};
const ownerAvatarPositions = {
1: { x: 0 + 53, y: 157 + 53, size: 106, rotation: -90 },
2: { x: 10 + 53, y: 153 + 53, size: 106, rotation: -90 },
3: { x: 3 + 53, y: 140 + 53, size: 106, rotation: -90 }
};
function stripHtml(html) {
if (!html) return '';
return html.replace(/<[^>]+>/g, '').trim();
}
function injectBaseStyles() {
const style = document.createElement('style');
style.textContent = `
#jue-canvas-container { user-select: none; }
#jue-canvas { cursor: default; }
#jue-custom-menu {
position: fixed; z-index: 10000; background: white;
border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);
padding: 4px 0; margin: 0; list-style: none; display: none;
}
#jue-custom-menu li {
padding: 6px 16px; cursor: pointer; white-space: nowrap;
}
#jue-custom-menu li:hover {
background: #f5f5f5;
}
`;
document.head.append(style);
}
function createCustomRightMenu() {
if (document.getElementById('jue-custom-menu')) {
customMenu = document.getElementById('jue-custom-menu');
return;
}
customMenu = document.createElement('ul');
customMenu.id = 'jue-custom-menu';
const downloadItem = document.createElement('li');
downloadItem.innerText = '下载图片';
downloadItem.addEventListener('click', function() {
downloadCanvasAsCustomName();
hideCustomMenu();
});
customMenu.append(downloadItem);
document.body.append(customMenu);
}
function showCustomMenu(e) {
if (!customMenu) createCustomRightMenu();
const menuWidth = customMenu.offsetWidth;
const menuHeight = customMenu.offsetHeight;
const winWidth = window.innerWidth;
const winHeight = window.innerHeight;
let left = e.clientX + 10;
let top = e.clientY + 10;
if (left + menuWidth > winWidth) left = e.clientX - menuWidth - 10;
if (top + menuHeight > winHeight) top = e.clientY - menuHeight - 10;
customMenu.style.left = `${left}px`;
customMenu.style.top = `${top}px`;
customMenu.style.display = 'block';
isMenuShow = true;
}
function hideCustomMenu() {
if (customMenu) {
customMenu.style.display = 'none';
isMenuShow = false;
}
}
function createConfirmModal() {
if (document.getElementById('jue-modal')) return;
const modal = document.createElement('div');
modal.id = 'jue-modal';
modal.style.cssText = `
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background: rgba(0,0,0,0.5); z-index: 9999; display: flex;
align-items: center; justify-content: center;
`;
const modalContent = document.createElement('div');
modalContent.id = 'jue-modal-content';
modalContent.style.cssText = `
background: white; padding: 24px; border-radius: 8px;
width: 90%; max-width: 400px; text-align: center;
`;
const modalText = document.createElement('p');
modalText.id = 'jue-modal-text';
const cleanVisitName = stripHtml(visitUser.username);
const cleanOwnerName = stripHtml(currentFloorData.ownerName);
modalText.innerText = `尊敬的${cleanVisitName},您似乎对${cleanOwnerName}有点想法?要不要Jue他?`;
modalText.style.marginBottom = '20px';
const btnContainer = document.createElement('div');
btnContainer.id = 'jue-modal-btn';
btnContainer.style.display = 'flex';
btnContainer.style.gap = '12px';
btnContainer.style.justifyContent = 'center';
const cancelBtn = document.createElement('button');
cancelBtn.innerText = '否';
cancelBtn.style.cssText = `
padding: 8px 24px; border: none; border-radius: 4px;
background: #eee; cursor: pointer;
`;
cancelBtn.onclick = () => modal.remove();
const confirmBtn = document.createElement('button');
confirmBtn.innerText = '是';
confirmBtn.style.cssText = `
padding: 8px 24px; border: none; border-radius: 4px;
background: #3b82f6; color: white; cursor: pointer;
`;
confirmBtn.onclick = handleJueConfirm;
btnContainer.append(cancelBtn, confirmBtn);
modalContent.append(modalText, btnContainer);
modal.append(modalContent);
document.body.append(modal);
}
function handleJueConfirm() {
const modal = document.getElementById('jue-modal');
const modalText = document.getElementById('jue-modal-text');
const btnContainer = document.getElementById('jue-modal-btn');
const juedUsersStr = localStorage.getItem('juedUsers') || '{}';
const juedUsers = JSON.parse(juedUsersStr);
const currentOwnerUid = currentFloorData.ownerUid;
if (juedUsers[currentOwnerUid] && Date.now() - juedUsers[currentOwnerUid] < JUE_COOLDOWN) {
const cleanOwnerName = stripHtml(currentFloorData.ownerName);
modalText.innerText = `您刚刚已经jue过${cleanOwnerName}了,休息休息保存体力吧~`;
btnContainer.remove();
setTimeout(() => modal.remove(), TIP_DURATION);
return;
}
juedUsers[currentOwnerUid] = Date.now();
localStorage.setItem('juedUsers', JSON.stringify(juedUsers));
modal.remove();
createJueEffectContainer();
loadImagesAndStartEffect();
}
function createJueEffectContainer() {
const existingContainer = document.getElementById('jue-effect-container');
if (existingContainer) existingContainer.remove();
hideCustomMenu();
const effectContainer = document.createElement('div');
effectContainer.id = 'jue-effect-container';
effectContainer.style.cssText = `
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
z-index: 9998; background: transparent; text-align: center;
`;
const canvasContainer = document.createElement('div');
canvasContainer.id = 'jue-canvas-container';
canvasContainer.style.cssText = `
margin: 0 auto; position: relative; width: 270px; height: 270px;
`;
canvas = document.createElement('canvas');
canvas.id = 'jue-canvas';
canvas.width = 270;
canvas.height = 270;
canvas.style.width = '100%';
canvas.style.height = '100%';
ctx = canvas.getContext('2d');
canvas.addEventListener('contextmenu', function(e) {
e.preventDefault();
showCustomMenu(e);
});
canvasContainer.append(canvas);
effectContainer.append(canvasContainer);
document.body.append(effectContainer);
}
function downloadCanvasAsCustomName() {
if (!canvas) return;
try {
const cleanVisitName = stripHtml(visitUser.username) || '未知访客';
const cleanOwnerName = (currentFloorData && stripHtml(currentFloorData.ownerName)) || '未知层主';
const fileName = `${cleanVisitName}jue${cleanOwnerName}.png`;
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = dataURL;
link.download = fileName;
link.click();
} catch (error) {
console.error('图片保存失败:', error);
alert('保存失败,可能存在跨域图片');
}
}
function loadImagesAndStartEffect() {
images.bg = [null, null, null];
images.visitAvatar = null;
images.ownerAvatar = null;
images.visitAvatarUrl = visitUser.avatarUrl || '/tool/why/img/default-avatar.png';
images.ownerAvatarUrl = currentFloorData.ownerAvatar || '/tool/why/img/default-avatar.png';
let loadedCount = 0;
const totalImages = 5;
for (let i = 1; i <= 3; i++) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = `/tool/why/img/jue/${i}.png`;
img.onload = function() {
images.bg[i] = img;
loadedCount++;
if (loadedCount === totalImages) startJueEffect();
};
img.onerror = function() {
console.error(`Failed to load background image ${i}`);
loadedCount++;
if (loadedCount === totalImages) startJueEffect();
};
}
const visitImg = new Image();
visitImg.crossOrigin = 'anonymous';
visitImg.src = images.visitAvatarUrl;
visitImg.onload = function() {
images.visitAvatar = visitImg;
loadedCount++;
if (loadedCount === totalImages) startJueEffect();
};
visitImg.onerror = handleImageError;
const ownerImg = new Image();
ownerImg.crossOrigin = 'anonymous';
ownerImg.src = images.ownerAvatarUrl;
ownerImg.onload = function() {
images.ownerAvatar = ownerImg;
loadedCount++;
if (loadedCount === totalImages) startJueEffect();
};
ownerImg.onerror = handleImageError;
function handleImageError() {
console.error("Failed to load avatar image");
loadedCount++;
if (loadedCount === totalImages) startJueEffect();
}
}
function drawCircularAvatar(img, x, y, size, rotation = 0) {
if (!img || !ctx) return;
ctx.save();
ctx.translate(x, y);
if (rotation !== 0) ctx.rotate(rotation * Math.PI / 180);
ctx.beginPath();
ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, -size / 2, -size / 2, size, size);
ctx.restore();
}
function drawCurrentFrame(frameNum) {
if (!ctx || !images.bg[frameNum]) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(images.bg[frameNum], 0, 0, canvas.width, canvas.height);
if (images.visitAvatar) {
const pos = visitAvatarPositions[frameNum];
drawCircularAvatar(images.visitAvatar, pos.x, pos.y, pos.size);
}
if (images.ownerAvatar) {
const pos = ownerAvatarPositions[frameNum];
drawCircularAvatar(images.ownerAvatar, pos.x, pos.y, pos.size, pos.rotation);
}
}
function animate(timestamp) {
if (!timestamp) timestamp = 0;
if (timestamp - lastFrameTime >= FRAME_DELAY) {
drawCurrentFrame(currentFrame + 1);
currentFrame = (currentFrame + 1) % 3;
lastFrameTime = timestamp;
}
animationFrameId = requestAnimationFrame(animate);
}
function startJueEffect() {
if (!smartbgm) {
smartbgm = new Audio('/tool/why/img/aaa.mp3');
smartbgm.loop = true;
}
smartbgm.play().catch(() => {});
currentFrame = 0;
lastFrameTime = 0;
drawCurrentFrame(1);
animationFrameId = requestAnimationFrame(animate);
setTimeout(() => {
stopJueEffect();
const effectContainer = document.getElementById('jue-effect-container');
if (effectContainer) effectContainer.remove();
hideCustomMenu();
}, EFFECT_DURATION);
}
function stopJueEffect() {
if (smartbgm) {
smartbgm.pause();
smartbgm.currentTime = 0;
}
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
function bindFloorHoverEvent() {
const floorTargets = document.querySelectorAll('.jue-floor-target');
if (floorTargets.length === 0) return;
floorTargets.forEach(target => {
target.addEventListener('mouseenter', function() {
currentFloorData = {
ownerUid: this.dataset.ownerUid || '',
ownerName: this.dataset.ownerName || '未知用户',
ownerAvatar: this.dataset.ownerAvatar || '/tool/why/img/default-avatar.png'
};
if (!currentFloorData.ownerUid || currentFloorData.ownerUid === visitUser.uid) return;
hoverTimer = setTimeout(createConfirmModal, HOVER_DELAY);
});
target.addEventListener('mouseleave', () => {
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
});
});
}
function bindGlobalMenuCloseEvent() {
document.addEventListener('click', function(e) {
if (isMenuShow && !customMenu.contains(e.target)) {
hideCustomMenu();
}
});
document.addEventListener('keydown', function(e) {
if (isMenuShow && e.key === 'Escape') {
hideCustomMenu();
}
});
}
injectBaseStyles();
createCustomRightMenu();
bindGlobalMenuCloseEvent();
bindFloorHoverEvent();
});
</script>
JavaScript
最最后,将附件下载解压到 /tool/why/ 目录下,或者你自定义位置解压,并修改上方 js 中的路径。
我用夸克网盘分享了「img_MG7YY.tar.gz」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/fb71c67c90a2
【版权声明】:服务器导航网所有内容均来自网络和部分原创,若无意侵犯到您的权利,请及时与联系 QQ 2232175042,将在48小时内删除相关内容!!



