ShengShengBuXi/ShengShengBuXi/Pages/Index.cshtml
2025-03-29 01:05:06 +08:00

722 lines
30 KiB
Plaintext
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.

@page
@model IndexModel
@{
// ViewData["Title"] = "Home page";
Layout = null;
}
<!doctype html>
<html>
<head>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="~/js/turn.min.js"></script>
<!-- 引入水波纹效果插件 -->
<script src="~/js/jquery.ripples.js"></script>
<!-- 引入SignalR客户端 -->
<script src="~/lib/microsoft-signalr/signalr.min.js"></script>
<style type="text/css">
/* 页面基础样式 */
body {
background: #ccc;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
/* 设置高度为视口高度 */
overflow: hidden;
/* 防止页面滚动 */
}
/* 杂志容器样式 */
#magazine {
width: 100%;
height: 100vh;
margin: 0 auto;
overflow: hidden;
}
/* 翻页效果的页面样式 */
#magazine .turn-page {
background-color: #ccc;
background-size: 100% 100%;
}
/* 文字倒影效果 */
.reflection-text {
-webkit-box-reflect: below 20px linear-gradient(transparent, rgba(0, 0, 0, 0.2));
}
.r-container-text {
font-size: 40px;
font-weight: 700;
font-style: italic;
text-align: left;
width: 70%;
}
/* 垂直文字容器样式 */
.vertical-text-container {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 70%;
margin-left: 15%;
padding-top: 10%;
}
/* 文字样式 */
.vertical-text {
text-orientation: mixed;
font-size: 16px;
margin: 9px 0px;
color: #333;
font-weight: 400;
width: 100%;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
}
.r-container {
display: flex;
justify-content: center;
align-items: center;
height: 90%;
width: 100%;
}
/* 设置奇数页的样式,包括页面右侧的阴影效果 */
#magazine .odd {
background-image: url(img/l.jpg);
background-size: 100% 100%;
}
/* 设置偶数页的样式,包括页面左侧的阴影效果 */
#magazine .even {
background-image: url(img/r.jpg);
background-size: 100% 100%;
}
</style>
</head>
<body>
<!-- 水波纹效果层 -->
<div class="water-effect"
style="background-color: transparent; background-size: 100% 100%;width: 100vw;height: 100vh;position: absolute;top: 0;left: 0; z-index: 999;">
</div>
<!-- 杂志内容容器 -->
<div id="magazine">
<!-- 封面 -->
<div id="bool_page_1" class="even"></div>
<!-- 第一页:文字内容,左侧文字 -->
<div id="bool_page_2" class="odd">
<div id="l_container_2" class="vertical-text-container">
</div>
</div>
<!-- 第二页:带倒影效果的文字,右侧文字 -->
<div id="bool_page_3" class="even">
<div class="r-container">
<div id="r_container_3" class="reflection-text r-container-text"></div>
</div>
</div>
</div>
<script type="text/javascript">
// 全局配置对象
const CONFIG = {
// 左侧容器配置
leftContainer: {
turnPageHeight: 0.55, // 左侧容器翻页的高度比例
fontSize: '16px', // 左侧列表文字大小
typewriterSpeed: 50 // 左侧文字打字机速度(毫秒)
},
// 右侧容器配置
rightContainer: {
fontSize: '40px', // 右侧文字大小
fontWeight: '700', // 右侧文字粗细
fontStyle: 'italic', // 右侧文字样式
typewriterSpeed: 2000, // 右侧文字打字机速度(毫秒)增加到2000毫秒
fadeChars: 4, // 渐变字符数量,值越大渐变效果越长
fadeStepTime: 500, // 字符透明度过渡时间(毫秒)
fadeDelayFactor: 0.25 // 每个字符的透明度递减因子(0-1)
},
// 水波纹效果配置
waterEffect: {
enabled: true, // 是否开启水波纹
minInterval: 1600, // 最小触发间隔(毫秒)
maxInterval: 8000, // 最大触发间隔(毫秒)
simultaneousDrops: 2, // 同时触发的波纹数量
fadeOutSpeed: 5000, // 文字渐隐效果的速度(毫秒)增加到5000毫秒
centerBias: 0.6, // 涟漪靠近中心区域的偏好值(0-1)
largeDrop: {
probability: 0.2, // 大涟漪出现的概率
size: 80 // 大涟漪的大小
}
},
};
let currentSentenceIndex = 0; // 当前句子索引
let isAnimating = false; // 动画状态标志
// 设置总页数为1000页实际上是循环使用有限的内容
var numberOfPages = 11;
/**
* 检查文字容器是否需要翻页
* @@param {string} containerSelector - 容器选择器
* @@returns {boolean} - 是否需要翻页
*/
function checkNeedTurnPage(containerSelector) {
const container = $(containerSelector);
const containerHeight = container.height();
const parentHeight = container.parent().height();
return (containerHeight > parentHeight * CONFIG.leftContainer.turnPageHeight);
}
/**
* 文字渐隐效果
* @@param {string} selector - 目标元素选择器
* @@param {Function} callback - 渐隐完成后的回调函数
*/
function fadeOutText(selector, callback) {
$(selector).fadeOut(CONFIG.waterEffect.fadeOutSpeed, function () {
$(this).html('');
$(this).show();
if (callback) callback();
});
}
/**
* 打字机效果函数(带渐显)
* @@param {string} selector - 目标元素选择器
* @@param {string} text - 要显示的文本
* @@param {number} speed - 打字速度(毫秒)
* @@param {Function} callback - 完成后的回调函数
*/
function typewriterEffect(selector, text, speed, callback) {
if (isAnimating) return;
isAnimating = true;
// 获取渐变相关配置
const fadeChars = CONFIG.rightContainer.fadeChars || 4; // 渐变字符数量
const fadeStepTime = CONFIG.rightContainer.fadeStepTime || 500; // 过渡时间
const fadeDelayFactor = CONFIG.rightContainer.fadeDelayFactor || 0.25; // 透明度递减因子
// 清空容器并设置初始可见度
$(selector).html('').css('opacity', 1);
// 创建所有字符元素但初始透明度为0
for (let i = 0; i < text.length; i++) {
const charSpan = $('<span></span>').text(text.charAt(i));
charSpan.css({
'opacity': 0,
'display': 'inline-block', // 确保每个字符是独立的块
'transition': `opacity ${fadeStepTime}ms ease-in-out` // 平滑的透明度过渡效果
});
$(selector).append(charSpan);
}
// 获取所有字符span元素
const chars = $(selector).find('span');
const totalChars = chars.length;
// 设置初始不透明度,形成从左到右的渐变效果
// 左侧字符较深,右侧字符较浅
const setInitialOpacity = () => {
for (let i = 0; i < totalChars; i++) {
// 计算不透明度:第一个字符不透明度最高,往后逐渐降低
let position = i / totalChars; // 字符在文本中的相对位置 (0-1)
let opacity = Math.max(0, 0.8 - (position * fadeChars * 0.2));
// 超出可见范围的字符完全透明
if (i >= fadeChars) {
opacity = 0;
}
// 设置不透明度
$(chars[i]).css('opacity', opacity);
}
};
// 执行渐变动画
const animateText = () => {
let progress = 0;
const step = () => {
if (progress >= 1) {
// 完成动画
chars.css('opacity', 1);
isAnimating = false;
if (callback) callback();
// 等待随机4-7秒后请求下一条文本
const delay = Math.floor(Math.random() * 3000) + 4000; // 4000-7000毫秒
console.log(`文本显示完成,将在${delay/1000}秒后请求下一条文本`);
setTimeout(requestNextText, delay);
return;
}
// 更新每个字符的透明度
for (let i = 0; i < totalChars; i++) {
let targetOpacity = 1; // 目标不透明度(完全显示)
let currentPosition = i / totalChars; // 字符的相对位置
// 计算当前动画进度下该位置的字符应有的透明度
// 小于当前进度的字符应该更不透明
let opacity = targetOpacity * Math.max(0, 1 - Math.max(0, (currentPosition - progress) * 3));
// 设置不透明度
$(chars[i]).css('opacity', opacity);
}
// 增加进度
progress += 0.02;
// 继续下一步动画
setTimeout(step, speed / 50);
};
// 开始动画
step();
};
// 设置初始不透明度并开始动画
setInitialOpacity();
setTimeout(animateText, 50);
}
/**
* 添加文字到左侧历史记录
* @@param {string} text - 要添加的文字
*/
function addToHistory(text, id) {
// 检查是否需要翻页
if (checkNeedTurnPage(id)) {
let currentPage = $("#magazine").turn("page");
if (numberOfPages <= currentPage + 1) {
//说明页数已经到最后一页了,跳转到之前页面
$('#magazine').turn('page', 2);
} else {
// 执行翻页
$('#magazine').turn('next');
}
console.log(currentPage, '翻页后');
let l_id = '#l_container_' + (currentPage);
setTimeout(() => {
let currentPage1 = $("#magazine").turn("page");
let l_id = '#l_container_' + (currentPage1);
addToHistory(text, l_id);
//把之前的页面清空掉
$('#l_container_' + (currentPage1 - 2)).html('');
$('#r_container_' + (currentPage1 - 1)).html('');
console.log('#r_container_' + (currentPage1 - 1), $('#r_container_' + (currentPage1 - 1)));
}, 600); // 增加延时以配合更慢的翻页速度
return false;
}
const container = $(id);
// 创建新的段落元素
const newP = $('<p class="vertical-text"></p>');
// 将新段落添加到容器的末尾(而不是开头)
container.append(newP);
// 设置初始透明度为0
newP.css('opacity', 0);
// 设置文本内容
newP.text(text);
// 整条文字渐显效果
newP.animate(
{ opacity: 1 },
2000, // 设置动画时长为2秒
'swing'
);
return true;
}
/**
* 显示用户说话内容
* @@param {string} text - 用户说话内容
*/
function displayText(text) {
if (isAnimating) return;
var currentPage = $("#magazine").turn("page");
let r_id = '#r_container_' + (currentPage + 1);
let l_id = '#l_container_' + (currentPage);
const rightText = $(r_id);
// 如果右侧已有文字,先淡出
if (rightText.text()) {
const originalText = rightText.text(); // 保存原文字
fadeOutText(r_id, function () {
// 将原文字添加到历史
let s = addToHistory(originalText, l_id);
if (s) {
typewriterEffect(r_id, text, CONFIG.rightContainer.typewriterSpeed);
} else {
setTimeout(() => {
displayText(text);
}, 500);
}
});
} else {
// 直接显示新文字
typewriterEffect(r_id, text, CONFIG.rightContainer.typewriterSpeed);
}
}
/**
* 添加页面到书本中的函数
* @@param {number} page - 页码
* @@param {object} book - 书本对象
*/
function addPage(page, book) {
// 首先检查页面是否已经在书本中
if (!book.turn('hasPage', page)) {
let page_type = page % 2;
let element = null;
if (page_type == 0) {
element = $('<div />', { 'class': 'page odd', 'id': 'bool_page_' + page })
.html('<div id="l_container_' + page + '" class="vertical-text-container"></div>'); // 添加空的内容区域
} else {
element = $('<div />', { 'class': 'page even', 'id': 'bool_page_' + page })
.html('<div class="r-container"><div id="r_container_' + page + '" class="reflection-text r-container-text"></div></div>'); // 添加空的内容区域
}
// 将页面添加到书本中
book.turn('addPage', element, page);
}
}
// SignalR连接变量
let hubConnection;
// 初始化SignalR连接
function initializeSignalRConnection() {
// 创建连接
hubConnection = new signalR.HubConnectionBuilder()
.withUrl("/audioHub")
.withAutomaticReconnect()
.build();
// 接收显示文本的处理函数
hubConnection.on("ReceiveDisplayText", function (text) {
console.log("收到显示文本:", text);
displayText(text);
});
// 接收显示配置的处理函数
hubConnection.on("ReceiveDisplayConfig", function (configJson) {
console.log("收到显示配置");
try {
const newConfig = JSON.parse(configJson);
// 更新全局配置
updateConfig(newConfig);
console.log("已更新显示配置");
} catch (error) {
console.error("解析显示配置失败:", error);
}
});
// 启动连接
hubConnection.start()
.then(function () {
console.log("SignalR连接成功");
// 注册为显示客户端
hubConnection.invoke("RegisterClient", 3, "Display")
.then(function() {
console.log("注册为显示客户端成功");
// 获取显示配置
return hubConnection.invoke("GetDisplayConfig");
})
.then(function(configJson) {
if(configJson) {
console.log("已获取显示配置");
try {
const config = JSON.parse(configJson);
updateConfig(config);
} catch (error) {
console.error("解析显示配置失败:", error);
}
}
// 开始请求第一条显示文本
requestNextText();
})
.catch(function(err) {
console.error("客户端操作失败:", err);
});
})
.catch(function (err) {
console.error("SignalR连接失败:", err);
// 5秒后重试
setTimeout(initializeSignalRConnection, 5000);
});
// 连接关闭的处理
hubConnection.onclose(function() {
console.log("SignalR连接已关闭尝试重新连接...");
setTimeout(initializeSignalRConnection, 5000);
});
}
// 更新配置
function updateConfig(newConfig) {
// 更新左侧容器配置
if (newConfig.leftContainer) {
CONFIG.leftContainer.turnPageHeight = newConfig.leftContainer.turnPageHeight || CONFIG.leftContainer.turnPageHeight;
CONFIG.leftContainer.fontSize = newConfig.leftContainer.fontSize || CONFIG.leftContainer.fontSize;
CONFIG.leftContainer.typewriterSpeed = newConfig.leftContainer.typewriterSpeed || CONFIG.leftContainer.typewriterSpeed;
// 应用字体大小到CSS
document.documentElement.style.setProperty('--left-font-size', CONFIG.leftContainer.fontSize);
}
// 更新右侧容器配置
if (newConfig.rightContainer) {
CONFIG.rightContainer.fontSize = newConfig.rightContainer.fontSize || CONFIG.rightContainer.fontSize;
CONFIG.rightContainer.fontWeight = newConfig.rightContainer.fontWeight || CONFIG.rightContainer.fontWeight;
CONFIG.rightContainer.fontStyle = newConfig.rightContainer.fontStyle || CONFIG.rightContainer.fontStyle;
CONFIG.rightContainer.typewriterSpeed = newConfig.rightContainer.typewriterSpeed || CONFIG.rightContainer.typewriterSpeed;
// 更新渐显效果相关的配置
if (newConfig.rightContainer.fadeChars !== undefined) {
CONFIG.rightContainer.fadeChars = newConfig.rightContainer.fadeChars;
}
if (newConfig.rightContainer.fadeStepTime !== undefined) {
CONFIG.rightContainer.fadeStepTime = newConfig.rightContainer.fadeStepTime;
}
if (newConfig.rightContainer.fadeDelayFactor !== undefined) {
CONFIG.rightContainer.fadeDelayFactor = newConfig.rightContainer.fadeDelayFactor;
}
// 应用到CSS
document.documentElement.style.setProperty('--right-font-size', CONFIG.rightContainer.fontSize);
document.documentElement.style.setProperty('--right-font-weight', CONFIG.rightContainer.fontWeight);
document.documentElement.style.setProperty('--right-font-style', CONFIG.rightContainer.fontStyle);
}
// 更新水波纹效果配置
if (newConfig.waterEffect) {
CONFIG.waterEffect.enabled = newConfig.waterEffect.enabled !== undefined ? newConfig.waterEffect.enabled : CONFIG.waterEffect.enabled;
CONFIG.waterEffect.minInterval = newConfig.waterEffect.minInterval || CONFIG.waterEffect.minInterval;
CONFIG.waterEffect.maxInterval = newConfig.waterEffect.maxInterval || CONFIG.waterEffect.maxInterval;
CONFIG.waterEffect.simultaneousDrops = newConfig.waterEffect.simultaneousDrops || CONFIG.waterEffect.simultaneousDrops;
CONFIG.waterEffect.fadeOutSpeed = newConfig.waterEffect.fadeOutSpeed || CONFIG.waterEffect.fadeOutSpeed;
CONFIG.waterEffect.centerBias = newConfig.waterEffect.centerBias !== undefined ? newConfig.waterEffect.centerBias : CONFIG.waterEffect.centerBias;
if (newConfig.waterEffect.largeDrop) {
CONFIG.waterEffect.largeDrop.probability = newConfig.waterEffect.largeDrop.probability !== undefined ?
newConfig.waterEffect.largeDrop.probability : CONFIG.waterEffect.largeDrop.probability;
CONFIG.waterEffect.largeDrop.size = newConfig.waterEffect.largeDrop.size || CONFIG.waterEffect.largeDrop.size;
}
// 应用水波纹效果配置
if ($('.water-effect').data('ripples')) {
// 如果ripples已初始化先销毁
$('.water-effect').ripples('destroy');
}
if (CONFIG.waterEffect.enabled) {
$('.water-effect').show();
$('.water-effect').ripples({
resolution: 1024,
dropRadius: 1.5,
perturbance: 0
});
} else {
$('.water-effect').hide();
}
}
console.log("配置已更新:", CONFIG);
}
// 页面加载完成后初始化
$(window).ready(function () {
$('#magazine').turn({
display: 'double',
acceleration: true,
pages: numberOfPages,
gradients: !$.isTouch,
elevation: 50,
duration: 2000, // 增加翻页动画时长到2000毫秒原来的两倍
when: {
// 翻页过程中的回调函数
turning: function (e, page, view) {
// 获取当前需要的页面范围
var range = $(this).turn('range', page);
console.log('当前页码:', range);
// 确保页面范围内的每一页都被添加到书本中
for (page = range[0]; page <= range[1]; page++) {
addPage(page, $(this));
// console.log('需要有的页码:', page);
}
},
turned: function (e, page) {
console.log('当前页码:', page);
}
}
});
// 清空左侧初始文字
$('.vertical-text-container').html('');
// 设置初始页面
$('#magazine').turn('page', 2);
// 初始化SignalR连接
initializeSignalRConnection();
// 不再需要模拟定时说话
// setInterval(simulateUserSpeak, 3000);
});
// 绑定键盘事件
$(window).bind('keydown', function (e) {
if (e.keyCode == 37)
$('#magazine').turn('previous');
else if (e.keyCode == 39)
$('#magazine').turn('next');
// 空格键请求下一条文本
else if (e.keyCode == 32) {
requestNextText();
}
});
// 初始化页面效果
$(function () {
// 设置CSS变量
document.documentElement.style.setProperty('--left-font-size', CONFIG.leftContainer.fontSize);
document.documentElement.style.setProperty('--right-font-size', CONFIG.rightContainer.fontSize);
document.documentElement.style.setProperty('--right-font-weight', CONFIG.rightContainer.fontWeight);
document.documentElement.style.setProperty('--right-font-style', CONFIG.rightContainer.fontStyle);
if (!CONFIG.waterEffect.enabled) {
$('.water-effect').hide();
} else {
$('.water-effect').ripples({
resolution: 1024,
dropRadius: 1.5,
perturbance: 0
});
/**
* 创建单个水波纹定时器
* @@returns {Function} 定时器函数
*/
function createRippleTimer() {
return function randomRipple() {
let width = $(".water-effect").width();
let height = $(".water-effect").height();
// 中心坐标
let centerX = width / 2;
let centerY = height / 2;
// 根据中心偏好生成随机位置
let randomX, randomY;
if (Math.random() > CONFIG.waterEffect.centerBias) {
// 随机位置
randomX = Math.random() * width;
randomY = Math.random() * height;
// 避免位于上1/3区域
if (randomY < height / 3) {
randomY += height / 3;
}
} else {
// 靠近中心位置
let radius = Math.min(width, height) * 0.3; // 中心区域半径
let angle = Math.random() * Math.PI * 2; // 随机角度
let distance = Math.random() * radius; // 随机距离但不超过radius
randomX = centerX + Math.cos(angle) * distance;
randomY = centerY + Math.sin(angle) * distance;
}
// 决定是否创建大涟漪
let dropSize = Math.random() < CONFIG.waterEffect.largeDrop.probability ?
CONFIG.waterEffect.largeDrop.size : 50;
triggerRipple(".water-effect", randomX, randomY, dropSize);
let nextTime = Math.random() * (CONFIG.waterEffect.maxInterval - CONFIG.waterEffect.minInterval) + CONFIG.waterEffect.minInterval;
setTimeout(randomRipple, nextTime);
};
}
// 创建多个水波纹定时器
for (let i = 0; i < CONFIG.waterEffect.simultaneousDrops; i++) {
const rippleTimer = createRippleTimer();
rippleTimer();
}
}
// 延迟2秒后开始打字机效果
// setTimeout(() => {
// typewriterEffect('#r_container_3', '记得每到夏天傍晚,您就摇看蒲扇坐在藤椅里,把切好的西瓜最甜那块硬塞给我。', 100);
// }, 2000);
});
/**
* 触发水波纹效果
* @@param {string} selector - 目标元素选择器
* @@param {number} x - X坐标
* @@param {number} y - Y坐标
* @@param {number} size - 涟漪大小
*/
function triggerRipple(selector, x, y, size = 50) {
$(selector).ripples("drop", x, y, size, 0.15);
}
// 页面可见性变化检测,用于处理页面切换到后台时暂停涟漪效果
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面不可见时,停止涟漪生成
console.log("页面切换到后台,停止涟漪生成");
// 可以添加额外逻辑来暂停或减少涟漪效果
} else {
// 页面可见时,可以恢复涟漪生成
console.log("页面回到前台,继续涟漪生成");
// 可以添加额外逻辑来恢复涟漪效果
// 如果需要,可以重置涟漪效果
if (CONFIG.waterEffect.enabled) {
$('.water-effect').ripples("destroy");
$('.water-effect').ripples({
resolution: 1024,
dropRadius: 1.5,
perturbance: 0
});
}
}
});
/**
* 请求下一条文本
*/
function requestNextText() {
if (hubConnection && hubConnection.state === signalR.HubConnectionState.Connected) {
console.log("正在向服务器请求下一条文本...");
hubConnection.invoke("GetNextDisplayText")
.catch(function(err) {
console.error("请求下一条文本失败:", err);
// 如果请求失败,稍后重试
setTimeout(requestNextText, 5000);
});
} else {
console.warn("SignalR连接未就绪无法请求下一条文本");
// 如果连接未就绪,稍后重试
setTimeout(requestNextText, 3000);
}
}
</script>
</body>
</html>