diff --git a/project/plugins.js b/project/plugins.js index 5473467..bddace9 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -3010,6 +3010,8 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = core.overupdate() if (main.dom.saveLoad && main.dom.saveLoad.style.display === "block") core.saveLoad.update(); + if (main.dom.book && main.dom.book.style.display === "block") + core.book.update() }; class StatusBar { @@ -22102,71 +22104,117 @@ let time=0 ]) => { return sx <= x && x <= dx && sy <= y && y <= dy; }; - /** - * 使用范例 - *const backbox = makeBox([15, 35], [210, 90]); - *if(inRect(pos,backbox)){} - */ } - this.drawBook = function (floorId = core.status.floorId) { - if (core.domStyle.isVertical) { //对竖屏进行绘制坐标变换,只需要考虑横屏绘制,竖屏将自适应转置 - core.maps._setHDCanvasSize(ctx, 416, 676) - ctx.save(); //保存设置 - ctx.translate(416, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 - } else { - core.maps._setHDCanvasSize(ctx, 676, 676) - } - //在这里写绘制内容,只写横屏就行 - ctx.restore(); //恢复变换前的坐标,否则将连续转置 - } + class Book { constructor() { - this.width = 325 + this.width = 300 this.height = 400 this.pagemax = 1 this.page = 0 - + this.paperpages = [] } - paperTexture(dir) { + paperTexture(dir, num = 0) { const textureCanvas = document.createElement('canvas'); - textureCanvas.width = 325; + textureCanvas.width = 300; textureCanvas.height = 400; const textureCtx = textureCanvas.getContext('2d'); - // 创建弯曲路径函数(复用) - const createCurvedPath = (ctx, offsetY = 0) => { - ctx.beginPath(); - ctx.moveTo(0, 20 + offsetY); - // 上边缘曲线 - const cp1x = 325 * 0.5; - const cp1y = offsetY; - ctx.quadraticCurveTo(cp1x, cp1y, 325, 20 + offsetY); - // 右侧直线 - ctx.lineTo(325, 400 + offsetY); - - // 下边缘曲线 - const cp2x = 325 * 0.5; - const cp2y = 380 + offsetY; - ctx.quadraticCurveTo(cp2x, cp2y, 0, 400 + offsetY); - - // 左侧直线 - ctx.closePath(); - }; - - // 创建弯曲书页的蒙版 const createPageCurveMask = () => { const curveCanvas = document.createElement('canvas'); - curveCanvas.width = 325; + curveCanvas.width = 300; curveCanvas.height = 400; const curveCtx = curveCanvas.getContext('2d'); - curveCtx.clearRect(0, 0, 325, 400); + curveCtx.clearRect(0, 0, 300, 400); - createCurvedPath(curveCtx); + // 创建带毛边的路径 + const createTornEdgePath = (ctx, offsetY = 0) => { + ctx.beginPath(); + + // 生成更自然的毛边函数 + const createTornEdge = (startX, startY, endX, endY, intensity, steps, top = false) => { + const points = []; + const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); + const baseStep = length / steps; + + + // 生成控制点 + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const x = startX + (endX - startX) * t; + const y = startY + (endY - startY) * t + // 使用噪声函数生成更自然的毛边 + const noise = Math.sin(t * Math.PI * 4) * intensity * 0.5 + + Math.sin(t * Math.PI * 8) * intensity * 0.3 + + Math.sin(t * Math.PI * 16) * intensity * 0.2; + + const perpendicularX = -(endY - startY) / length; + const perpendicularY = (endX - startX) / length; + if (t < 0.2 || t > 0.8) points.push({ + x: x, + y: y + }); + else + points.push({ + x: x + perpendicularX * noise * (0.8 + Math.random() * 0.4), + y: y + top * perpendicularY * noise * (0.8 + Math.random() * 0.4) + }); + + } + + // 绘制毛边路径 + ctx.lineTo(points[0].x, points[0].y); + for (let i = 1; i < points.length; i++) { + const p = points[i]; + const prev = points[i - 1]; + + // 使用二次贝塞尔曲线使过渡更平滑 + const cpX = (prev.x + p.x) / 2; + const cpY = (prev.y + p.y) / 2; + ctx.quadraticCurveTo(cpX, cpY, p.x, p.y); + } + }; + ctx.moveTo(0, offsetY); + + // 上边缘曲线(添加毛边) + + createTornEdge(0, offsetY, 300, offsetY, 16, 24, true); + + + // 右侧边缘(根据方向决定是否添加毛边) + if (dir === "right") { + createTornEdge(300, offsetY, 300, 400 + offsetY, 16, 24); + } else { + ctx.lineTo(300, 400 + offsetY); + } + + // 下边缘曲线(添加毛边) + + createTornEdge(300, 400 + offsetY, 0, 400 + offsetY, 16, 24, true); + + + // 左侧边缘(根据方向决定是否添加毛边) + if (dir === "left") { + createTornEdge(0, 400 + offsetY, 0, offsetY, 16, 24); + } else { + ctx.lineTo(0, offsetY); + } + + ctx.closePath(); + }; + + // 创建上蒙版(顶部有毛边) + createTornEdgePath(curveCtx, 0, true); + curveCtx.fillStyle = 'white'; + curveCtx.fill(); + + // 创建下蒙版(底部有毛边) + curveCtx.clearRect(0, 0, 300, 400); + createTornEdgePath(curveCtx, 0, false); curveCtx.fillStyle = 'white'; curveCtx.fill(); @@ -22177,27 +22225,27 @@ let time=0 // 创建书页内容 (400px高,居中放置) const createPageContent = () => { const contentCanvas = document.createElement('canvas'); - contentCanvas.width = 325; + contentCanvas.width = 300; contentCanvas.height = 400; const contentCtx = contentCanvas.getContext('2d'); // 生成羊皮纸基础色(更精细的暖色调范围) const baseHue = 45; // 35-60黄色到橙色范围 const baseSaturation = 40; // 25-50%饱和度 - const baseLightness = 85; // 80-95%亮度 + const baseLightness = 70; // 80-95%亮度 // 创建高度自然的基底纹理 const createOrganicBase = () => { // 基础底色(轻微噪点纹理) contentCtx.fillStyle = `hsl(${baseHue}, ${baseSaturation}%, ${baseLightness}%)`; - contentCtx.fillRect(0, 0, 325, 400); + contentCtx.fillRect(0, 0, 300, 400); // 生成有机噪点纹理(模拟羊皮纸纤维) - const noiseData = contentCtx.createImageData(325, 400); + const noiseData = contentCtx.createImageData(300, 400); for (let i = 0; i < noiseData.data.length; i += 4) { // Perlin噪声生成更自然的纹理 - const x = (i / 4) % 325; - const y = Math.floor((i / 4) / 325); + const x = (i / 4) % 300; + const y = Math.floor((i / 4) / 300); const noiseVal = this.perlinNoise(x / 50, y / 50) * 0.5 + this.perlinNoise(x / 20, y / 20) * 0.3 + this.perlinNoise(x / 5, y / 5) * 0.2; @@ -22250,9 +22298,9 @@ let time=0 // 右侧边缘破损 if (Math.random() > 0.3) { const y = Math.random() * 350 + 25; - createOrganicTear(325, y, size, -Math.PI / 2, roughness); + createOrganicTear(300, y, size, -Math.PI / 2, roughness); } else { - const x = 275 + Math.random() * 50; + const x = 250 + Math.random() * 50; const y = Math.random() > 0.5 ? 0 : 400; createOrganicTear(x, y, size * 0.7, Math.random() > 0.5 ? 0 : Math.PI, roughness); } @@ -22284,7 +22332,7 @@ let time=0 contentCtx.closePath(); // 破损内部阴影 - contentCtx.fillStyle = `rgba(140, 110, 80, ${0.08 + Math.random()*0.07})`; + contentCtx.fillStyle = `rgba(140, 110, 80, ${0.18 + Math.random()*0.07})`; contentCtx.fill(); // 破损边缘线 @@ -22307,7 +22355,7 @@ let time=0 } contentCtx.lineWidth = 0.1 + Math.random() * 0.2; - contentCtx.strokeStyle = `rgba(120, 90, 70, ${0.2 + Math.random()*0.1})`; + contentCtx.strokeStyle = `rgba(120, 90, 70, ${0.3 + Math.random()*0.1})`; contentCtx.stroke(); } } @@ -22321,34 +22369,25 @@ let time=0 const shadowHue = baseHue - 8; const shadowSaturation = baseSaturation + 15; const shadowLightness = baseLightness * 0.55; - const shadowSize = 35; + const shadowSize = 10; const shadowCanvas = document.createElement('canvas'); - shadowCanvas.width = 325; + shadowCanvas.width = 300; shadowCanvas.height = 400; const shadowCtx = shadowCanvas.getContext('2d'); - // 创建临时canvas用于检测弯曲区域 - const tempCanvas = document.createElement('canvas'); - tempCanvas.width = 325; - tempCanvas.height = 400; - const tempCtx = tempCanvas.getContext('2d'); - createCurvedPath(tempCtx); - tempCtx.fillStyle = 'white'; - tempCtx.fill(); - const maskData = tempCtx.getImageData(0, 0, 325, 400).data; // 计算每个像素的阴影强度 - const gradientData = shadowCtx.createImageData(325, 400); + const gradientData = shadowCtx.createImageData(300, 400); for (let i = 0; i < gradientData.data.length; i += 4) { - const x = (i / 4) % 325; - const y = Math.floor((i / 4) / 325); + const x = (i / 4) % 300; + const y = Math.floor((i / 4) / 300); // 基础距离计算 const leftDist = x / shadowSize * (dir === "left" ? 1 : 0.7); - const rightDist = (325 - x) / shadowSize * (dir === "left" ? 0.7 : 1); - const topDist = (y - Math.abs(x - 162.5) / 162.5 * 20) / shadowSize; - const bottomDist = (400 - y - (1 - Math.abs(x - 162.5) / 162.5) * 20) / shadowSize; + const rightDist = (300 - x) / shadowSize * (dir === "left" ? 0.7 : 1); + const topDist = y / shadowSize; + const bottomDist = (400 - y) / shadowSize; // 根据方向调整左右阴影强度 let leftShadowIntensity = 0.7; @@ -22362,9 +22401,9 @@ let time=0 // 计算角部距离 const tlDist = Math.sqrt(x * x + y * y) / (shadowSize * 1.4); - const trDist = Math.sqrt((325 - x) * (325 - x) + y * y) / (shadowSize * 1.4); + const trDist = Math.sqrt((300 - x) * (300 - x) + y * y) / (shadowSize * 1.4); const blDist = Math.sqrt(x * x + (400 - y) * (400 - y)) / (shadowSize * 1.4); - const brDist = Math.sqrt((325 - x) * (325 - x) + (400 - y) * (400 - y)) / (shadowSize * 1.4); + const brDist = Math.sqrt((300 - x) * (300 - x) + (400 - y) * (400 - y)) / (shadowSize * 1.4); // 综合阴影强度 let shadowAlpha = 0; @@ -22423,22 +22462,22 @@ let time=0 const createHighlight = () => { const highlightCanvas = document.createElement('canvas'); - highlightCanvas.width = 325; + highlightCanvas.width = 300; highlightCanvas.height = 400; const highlightCtx = highlightCanvas.getContext('2d'); - highlightCtx.clearRect(0, 0, 325, 400); + highlightCtx.clearRect(0, 0, 300, 400); // 根据方向设置高光位置 const gradient = dir === 'right' ? - highlightCtx.createLinearGradient(10, 0, 50, 0) : - highlightCtx.createLinearGradient(275, 0, 315, 0); + highlightCtx.createLinearGradient(0, 0, 20, 0) : + highlightCtx.createLinearGradient(280, 0, 300, 0); - gradient.addColorStop(0, 'rgba(255,255,255,0)'); - gradient.addColorStop(0.5, 'rgba(255,255,255,0.5)'); - gradient.addColorStop(1, 'rgba(255,255,255,0)'); + gradient.addColorStop(0, 'rgba(207,191,150,0.5)'); + gradient.addColorStop(0.2, 'rgba(207,191,150,0.3)'); + gradient.addColorStop(1, 'rgba(207,191,150,0)'); highlightCtx.fillStyle = gradient; - highlightCtx.fillRect(dir === 'right' ? 10 : 275, 0, 40, 400); + highlightCtx.fillRect(dir === 'right' ? 0 : 280, 0, 20, 400); return highlightCanvas; @@ -22457,7 +22496,7 @@ let time=0 const addHyperRealisticDetails = () => { // 3.1 纤维纹理(模拟羊皮纸纤维) for (let i = 0; i < 2000; i++) { - const x = Math.random() * 325; + const x = Math.random() * 300; const y = Math.random() * 400; const length = 1 + Math.random() * 4; const angle = Math.random() * Math.PI * 2; @@ -22483,7 +22522,7 @@ let time=0 // 自然划痕系统 for (let i = 0; i < 10 + Math.random() * 10; i++) { const scratchLength = 15 + Math.random() * 60; - const startX = Math.random() * 325; + const startX = Math.random() * 300; const startY = Math.random() * 400; const angle = Math.random() * Math.PI * 2; const curveIntensity = 5 + Math.random() * 10; @@ -22550,7 +22589,7 @@ let time=0 if (stainType === 1) { // 指纹污渍 - const centerX = Math.random() * 325; + const centerX = Math.random() * 300; const centerY = Math.random() * 400; const size = 10 + Math.random() * 15; const rotation = Math.random() * Math.PI * 2; @@ -22578,7 +22617,7 @@ let time=0 } } else { // 老化斑点 - const centerX = Math.random() * 325; + const centerX = Math.random() * 300; const centerY = Math.random() * 400; const size = 5 + Math.random() * 15; const points = 8 + Math.floor(Math.random() * 6); @@ -22615,60 +22654,61 @@ let time=0 contentCtx.fill(); } } + + }; + + + + addHyperRealisticDetails(); return contentCanvas; - }; + } // 创建弯曲阴影(新增) const createCurvedShadow = () => { const shadowCanvas = document.createElement('canvas'); - shadowCanvas.width = 325; + shadowCanvas.width = 300; shadowCanvas.height = 400; const shadowCtx = shadowCanvas.getContext('2d'); - shadowCtx.clearRect(0, 0, 325, 400); + shadowCtx.clearRect(0, 0, 300, 400); // 绘制顶部阴影(跟随曲线) const topShadow = shadowCtx.createLinearGradient(0, 0, 0, 50); topShadow.addColorStop(0, 'rgba(0,0,0,0.25)'); topShadow.addColorStop(1, 'rgba(0,0,0,0)'); - shadowCtx.save(); - createCurvedPath(shadowCtx); - shadowCtx.clip(); + shadowCtx.fillStyle = topShadow; - shadowCtx.fillRect(0, 0, 325, 50); - shadowCtx.restore(); + shadowCtx.fillRect(0, 0, 300, 50); + // 绘制底部阴影(跟随曲线) const bottomShadow = shadowCtx.createLinearGradient(0, 350, 0, 400); bottomShadow.addColorStop(0, 'rgba(0,0,0,0)'); bottomShadow.addColorStop(1, 'rgba(0,0,0,0.25)'); - shadowCtx.save(); - createCurvedPath(shadowCtx); - shadowCtx.clip(); + shadowCtx.fillStyle = bottomShadow; - shadowCtx.fillRect(0, 350, 325, 50); - shadowCtx.restore(); + shadowCtx.fillRect(0, 350, 300, 50); + return shadowCanvas; }; // 应用弯曲变形 - const applyPageCurve = (content, mask, shadow) => { + const applyPageCurve = (content, mask) => { const resultCanvas = document.createElement('canvas'); - resultCanvas.width = 325; + resultCanvas.width = 300; resultCanvas.height = 400; const resultCtx = resultCanvas.getContext('2d'); - resultCtx.clearRect(0, 0, 325, 400); + resultCtx.clearRect(0, 0, 300, 400); + - // 先绘制阴影(在内容下方) - resultCtx.drawImage(shadow, 0, 0); // 绘制内容并应用蒙版 - resultCtx.drawImage(content, 0, 10); + resultCtx.drawImage(content, 0, 0); resultCtx.globalCompositeOperation = 'destination-in'; resultCtx.drawImage(mask, 0, 0); resultCtx.globalCompositeOperation = 'source-over'; @@ -22679,8 +22719,7 @@ let time=0 // 主流程 const mask = createPageCurveMask(); const content = createPageContent(); - const shadow = createCurvedShadow(); - const curvedPage = applyPageCurve(content, mask, shadow); + const curvedPage = applyPageCurve(content, mask); // 最终绘制 textureCtx.drawImage(curvedPage, 0, 0); @@ -22790,93 +22829,655 @@ let time=0 b: Math.round(b * 255) }; } + + background() { book.style.display = "block" - this.leftbackground = this.paperTexture('left') - this.rightbackground = this.paperTexture('right') - if (core.domStyle.isVertical) { //对竖屏进行绘制坐标变换,只需要考虑横屏绘制,竖屏将自适应转置 + + if (core.domStyle.isVertical) { core.maps._setHDCanvasSize(ctx, 416, 676) - ctx.save(); //保存设置 - ctx.translate(416, 0); //重新定位右上角为基准 - ctx.rotate(Math.PI / 2); //旋转90度 + ctx.save(); + ctx.translate(416, 0); + ctx.rotate(Math.PI / 2); } else { core.maps._setHDCanvasSize(ctx, 676, 416) - ctx.save(); //保存设置 + ctx.save(); } + + // 1. 先绘制背景底色 core.fillRect(ctx, 0, 0, 676, 416, "#000000") - //在这里写绘制内容,只写横屏就行 - ctx.beginPath() + + // 2. 绘制书脊和封面背景 const dx = (676 - this.width * 2) / 2, - dy = 416 - this.height - ctx.moveTo(0, 416) - ctx.lineTo(dx, this.height) - ctx.lineTo(dx, 20) - ctx.lineTo(0, dy + 20) - ctx.closePath() - ctx.fillStyle = `rgb(${248*0.7}, ${244*0.7}, ${230*0.7})` - ctx.fill() - ctx.beginPath() - ctx.moveTo(676, 416) - ctx.lineTo(676 - dx, this.height) - ctx.lineTo(676 - dx, 20) - ctx.lineTo(676, dy + 20) - ctx.closePath() - ctx.fill() - ctx.strokeStyle = `rgb(${248*0.5}, ${244*0.5}, ${230*0.5})`; - ctx.beginPath() - ctx.moveTo(2, 100) - ctx.lineTo(2, 130) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(5, 320) - ctx.lineTo(5, 290) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(6, 240) - ctx.lineTo(6, 260) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(10, 340) - ctx.lineTo(10, 360) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(665, 100) - ctx.lineTo(665, 130) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(670, 190) - ctx.lineTo(670, 160) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(671, 50) - ctx.lineTo(671, 130) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(673, 350) - ctx.lineTo(673, 370) - ctx.stroke() - core.drawImage(ctx, this.leftbackground, 0, 0, 325, 400, dx, 0, this.width, this.height) - core.drawImage(ctx, this.rightbackground, 0, 0, 325, 400, dx + this.width, 0, this.width, this.height) - - ctx.beginPath() - ctx.moveTo(0, 416) - ctx.lineTo(dx, this.height) - ctx.quadraticCurveTo(this.width / 2 + dx, this.height - 20, 338, this.height) - ctx.quadraticCurveTo(this.width * 3 / 2 + dx, this.height - 20, 676 - dx, this.height) - ctx.lineTo(676, 416) - ctx.quadraticCurveTo(676 - this.width / 2, 396, 676 - this.width, 416) - ctx.quadraticCurveTo(338, 406, this.width, 416) - ctx.quadraticCurveTo(this.width / 2, 396, 0, 416) - ctx.closePath() - ctx.fillStyle = '#706b61' - ctx.fill() + dy = (416 - this.height) / 2 - ctx.restore(); //恢复变换前的坐标,否则将连续转置 + //绘制打开的书皮,416*676,暗红色 + const bookWidth = 676; + const bookHeight = 416; + const spineWidth = 26; + const spineX = bookWidth / 2; + + const foldSize = 8; // 折角大小 + + // 左侧连接处折角 + ctx.fillStyle = "rgba(60, 40, 20, 0.6)"; + ctx.beginPath(); + // 上部折角 + ctx.moveTo(spineX - spineWidth / 2, 0); + ctx.lineTo(spineX - spineWidth / 2 - foldSize, foldSize); + ctx.lineTo(spineX - spineWidth / 2, foldSize * 2); + // 下部折角 + ctx.moveTo(spineX - spineWidth / 2, bookHeight); + ctx.lineTo(spineX - spineWidth / 2 - foldSize, bookHeight - foldSize); + ctx.lineTo(spineX - spineWidth / 2, bookHeight - foldSize * 2); + ctx.fill(); + + // 右侧连接处折角 + ctx.beginPath(); + // 上部折角 + ctx.moveTo(spineX + spineWidth / 2, 0); + ctx.lineTo(spineX + spineWidth / 2 + foldSize, foldSize); + ctx.lineTo(spineX + spineWidth / 2, foldSize * 2); + // 下部折角 + ctx.moveTo(spineX + spineWidth / 2, bookHeight); + ctx.lineTo(spineX + spineWidth / 2 + foldSize, bookHeight - foldSize); + ctx.lineTo(spineX + spineWidth / 2, bookHeight - foldSize * 2); + ctx.fill(); + + // 添加折角高光效果 + ctx.strokeStyle = "rgba(200, 180, 150, 0.3)"; + ctx.lineWidth = 1; + // 左侧高光 + ctx.beginPath(); + ctx.moveTo(spineX - spineWidth / 2, foldSize * 2); + ctx.lineTo(spineX - spineWidth / 2 - foldSize / 2, foldSize * 1.5); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(spineX - spineWidth / 2, bookHeight - foldSize * 2); + ctx.lineTo(spineX - spineWidth / 2 - foldSize / 2, bookHeight - foldSize * 1.5); + ctx.stroke(); + // 右侧高光 + ctx.beginPath(); + ctx.moveTo(spineX + spineWidth / 2, foldSize * 2); + ctx.lineTo(spineX + spineWidth / 2 + foldSize / 2, foldSize * 1.5); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(spineX + spineWidth / 2, bookHeight - foldSize * 2); + ctx.lineTo(spineX + spineWidth / 2 + foldSize / 2, bookHeight - foldSize * 1.5); + ctx.stroke(); + // 创建更丰富的书皮渐变 + const coverGradient = ctx.createLinearGradient(0, 0, bookWidth, 0); + coverGradient.addColorStop(0, "#6b3a3a"); + coverGradient.addColorStop(0.49, "#4a2222"); + coverGradient.addColorStop(0.5, "#4a2222"); + coverGradient.addColorStop(0.51, "#4a2222"); + coverGradient.addColorStop(1, "#6b3a3a"); + + // 绘制书皮主体 + ctx.fillStyle = coverGradient; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(bookWidth, 0); + ctx.lineTo(bookWidth, bookHeight); + ctx.lineTo(0, bookHeight); + ctx.closePath(); + ctx.fill(); + + // 添加精美的边缘花纹 + + ctx.strokeStyle = "rgba(180, 140, 100, 0.4)"; + ctx.lineWidth = 2; + + // 顶部花纹 + ctx.beginPath(); + for (let x = 20; x < bookWidth; x += 40) { + ctx.moveTo(x, 5); + ctx.lineTo(x + 10, 15); + ctx.lineTo(x + 20, 5); + } + ctx.stroke(); + + // 底部花纹 + ctx.beginPath(); + for (let x = 10; x < bookWidth; x += 40) { + ctx.moveTo(x, bookHeight - 5); + ctx.lineTo(x + 15, bookHeight - 15); + ctx.lineTo(x + 30, bookHeight - 5); + } + ctx.stroke(); + + + // 添加精美的书脊装饰 + + + + // 书脊主线条 + ctx.strokeStyle = "rgba(120, 80, 60, 0.8)"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(spineX - spineWidth / 2, 0); + ctx.lineTo(spineX - spineWidth / 2, bookHeight); + ctx.moveTo(spineX + spineWidth / 2, 0); + ctx.lineTo(spineX + spineWidth / 2, bookHeight); + ctx.stroke(); + + // 书脊装饰花纹 - 古典风格 + ctx.fillStyle = "rgba(200, 160, 120, 0.3)"; + for (let y = 50; y < bookHeight; y += 80) { + // 花纹单元 + ctx.beginPath(); + ctx.moveTo(spineX, y - 15); + ctx.lineTo(spineX - 8, y - 5); + ctx.lineTo(spineX - 12, y + 5); + ctx.lineTo(spineX - 8, y + 15); + ctx.lineTo(spineX, y + 20); + ctx.lineTo(spineX + 8, y + 15); + ctx.lineTo(spineX + 12, y + 5); + ctx.lineTo(spineX + 8, y - 5); + ctx.closePath(); + ctx.fill(); + + // 连接线 + ctx.strokeStyle = "rgba(180, 140, 100, 0.4)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(spineX, y + 20); + ctx.lineTo(spineX, y + 60); + ctx.stroke(); + } + + + // 添加更精细的阴影效果 + + ctx.shadowColor = "rgba(0, 0, 0, 0.6)"; + ctx.shadowBlur = 20; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + + // 重新绘制边缘以应用阴影 + ctx.strokeStyle = "rgba(80, 50, 30, 0.8)"; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.rect(2, 2, bookWidth - 4, bookHeight - 4); + ctx.stroke(); + + + // 添加精细的皮质纹理 + + ctx.fillStyle = "rgba(40, 25, 15, 0.08)"; + + // 水平纹理 + for (let y = 10; y < bookHeight; y += 8) { + ctx.beginPath(); + ctx.moveTo(0, y); + for (let x = 0; x < bookWidth; x += 20) { + ctx.lineTo(x, y + (x % 40 < 20 ? 2 : -2)); + } + ctx.lineTo(bookWidth, y); + ctx.lineTo(bookWidth, y + 1); + for (let x = bookWidth; x > 0; x -= 20) { + ctx.lineTo(x, y + 1 + (x % 40 < 20 ? -1 : 1)); + } + ctx.closePath(); + ctx.fill(); + } + + // 垂直纹理 + ctx.fillStyle = "rgba(30, 20, 10, 0.05)"; + for (let x = 10; x < bookWidth; x += 12) { + ctx.beginPath(); + ctx.moveTo(x, 0); + for (let y = 0; y < bookHeight; y += 20) { + ctx.lineTo(x + (y % 40 < 20 ? 1 : -1), y); + } + ctx.lineTo(x + 1, bookHeight); + ctx.lineTo(x - 1, bookHeight); + for (let y = bookHeight; y > 0; y -= 20) { + ctx.lineTo(x + (y % 40 < 20 ? -1 : 1), y); + } + ctx.closePath(); + ctx.fill(); + } + + + // 添加精美的书角金属装饰 + + const cornerSize = 30; + const cornerGradient = ctx.createLinearGradient(0, 0, cornerSize, cornerSize); + cornerGradient.addColorStop(0, "rgba(200, 170, 100, 0.8)"); + cornerGradient.addColorStop(1, "rgba(150, 120, 70, 0.8)"); + + // 四个角 + + + + let [x, y] = [0, 0]; + // 绘制三角形装饰 + ctx.fillStyle = cornerGradient; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + cornerSize, y); + ctx.lineTo(x, y + cornerSize); + ctx.closePath(); + ctx.fill(); + + // 绘制弧线装饰 + ctx.strokeStyle = "rgba(80, 60, 30, 0.8)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, cornerSize * 0.7, 0, Math.PI / 2); + ctx.stroke(); + + // 绘制放射线装饰 + ctx.beginPath(); + for (let i = 0; i < 5; i++) { + const a = i * Math.PI / 10; + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(a) * cornerSize, y + Math.sin(a) * cornerSize); + } + ctx.stroke(); + + [x, y] = [bookWidth, 0] + + // 绘制三角形装饰(水平翻转) + ctx.fillStyle = cornerGradient; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x - cornerSize, y); + ctx.lineTo(x, y + cornerSize); + ctx.closePath(); + ctx.fill(); + + // 绘制弧线装饰(水平翻转) + ctx.strokeStyle = "rgba(80, 60, 30, 0.8)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, cornerSize * 0.7, Math.PI / 2, Math.PI); + ctx.stroke(); + + // 绘制放射线装饰(水平翻转) + ctx.beginPath(); + for (let i = 0; i < 5; i++) { + const a = Math.PI - i * Math.PI / 10; + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(a) * cornerSize, y + Math.sin(a) * cornerSize); + } + ctx.stroke(); + [x, y] = [0, bookHeight] + + // 绘制三角形装饰(垂直翻转) + ctx.fillStyle = cornerGradient; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + cornerSize, y); + ctx.lineTo(x, y - cornerSize); + ctx.closePath(); + ctx.fill(); + + // 绘制弧线装饰(垂直翻转) + ctx.strokeStyle = "rgba(80, 60, 30, 0.8)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, cornerSize * 0.7, -Math.PI / 2, 0); + ctx.stroke(); + + // 绘制放射线装饰(垂直翻转) + ctx.beginPath(); + for (let i = 0; i < 5; i++) { + const a = -i * Math.PI / 10; + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(a) * cornerSize, y + Math.sin(a) * cornerSize); + } + ctx.stroke(); + x = bookWidth; + y = bookHeight; + + // 绘制三角形装饰(水平和垂直翻转) + ctx.fillStyle = cornerGradient; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x - cornerSize, y); + ctx.lineTo(x, y - cornerSize); + ctx.closePath(); + ctx.fill(); + + // 绘制弧线装饰(水平和垂直翻转) + ctx.strokeStyle = "rgba(80, 60, 30, 0.8)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, cornerSize * 0.7, Math.PI, Math.PI * 3 / 2); + ctx.stroke(); + + // 绘制放射线装饰(水平和垂直翻转) + ctx.beginPath(); + for (let i = 0; i < 5; i++) { + const a = Math.PI + i * Math.PI / 10; + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(a) * cornerSize, y + Math.sin(a) * cornerSize); + } + ctx.stroke(); + + // 外边缘厚阴影 + ctx.shadowColor = "rgba(0, 0, 0, 0.7)"; + ctx.shadowBlur = 15; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + + ctx.strokeStyle = "rgba(80, 50, 30, 0.8)"; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.rect(2, 2, bookWidth - 4, bookHeight - 4); + ctx.stroke(); + + // 内边缘阴影(模拟厚度) + ctx.shadowColor = "transparent"; + const innerShadowWidth = 8; + + // 顶部内阴影 + const topShadow = ctx.createLinearGradient(0, 0, 0, innerShadowWidth); + topShadow.addColorStop(0, "rgba(0, 0, 0, 0.4)"); + topShadow.addColorStop(1, "transparent"); + ctx.fillStyle = topShadow; + ctx.fillRect(0, 0, bookWidth, innerShadowWidth); + + // 底部内阴影 + const bottomShadow = ctx.createLinearGradient(0, bookHeight, 0, bookHeight - innerShadowWidth); + bottomShadow.addColorStop(0, "rgba(0, 0, 0, 0.4)"); + bottomShadow.addColorStop(1, "transparent"); + ctx.fillStyle = bottomShadow; + ctx.fillRect(0, bookHeight - innerShadowWidth, bookWidth, innerShadowWidth); + + // 左侧内阴影 + const leftShadow = ctx.createLinearGradient(0, 0, innerShadowWidth, 0); + leftShadow.addColorStop(0, "rgba(0, 0, 0, 0.4)"); + leftShadow.addColorStop(1, "transparent"); + ctx.fillStyle = leftShadow; + ctx.fillRect(0, 0, innerShadowWidth, bookHeight); + + // 右侧内阴影 + const rightShadow = ctx.createLinearGradient(bookWidth, 0, bookWidth - innerShadowWidth, 0); + rightShadow.addColorStop(0, "rgba(0, 0, 0, 0.4)"); + rightShadow.addColorStop(1, "transparent"); + ctx.fillStyle = rightShadow; + ctx.fillRect(bookWidth - innerShadowWidth, 0, innerShadowWidth, bookHeight); + + // 书脊两侧的特殊阴影 + + const spineShadowWidth = 4; + + // 书脊左侧阴影 + const spineLeftShadow = ctx.createLinearGradient( + spineX - spineWidth / 2, 0, + spineX - spineWidth / 2 - spineShadowWidth, 0 + ); + spineLeftShadow.addColorStop(0, "rgba(0, 0, 0, 0.5)"); + spineLeftShadow.addColorStop(1, "transparent"); + ctx.fillStyle = spineLeftShadow; + ctx.fillRect( + spineX - spineWidth / 2 - spineShadowWidth, 0, + spineShadowWidth, bookHeight + ); + + // 书脊右侧阴影 + const spineRightShadow = ctx.createLinearGradient( + spineX + spineWidth / 2, 0, + spineX + spineWidth / 2 + spineShadowWidth, 0 + ); + spineRightShadow.addColorStop(0, "rgba(0, 0, 0, 0.5)"); + spineRightShadow.addColorStop(1, "transparent"); + ctx.fillStyle = spineRightShadow; + ctx.fillRect( + spineX + spineWidth / 2, 0, + spineShadowWidth, bookHeight + ); + + const pageLiftHeight = 20; + + // 左侧书页翻起效果 + ctx.fillStyle = "rgba(250, 240, 230, 0.7)"; + ctx.beginPath(); + ctx.moveTo(spineX - spineWidth / 2, bookHeight / 2 - pageLiftHeight); + ctx.lineTo(spineX - spineWidth / 2 - 5, bookHeight / 2); + ctx.lineTo(spineX - spineWidth / 2, bookHeight / 2 + pageLiftHeight); + ctx.closePath(); + ctx.fill(); + + // 右侧书页翻起效果 + ctx.beginPath(); + ctx.moveTo(spineX + spineWidth / 2, bookHeight / 2 - pageLiftHeight); + ctx.lineTo(spineX + spineWidth / 2 + 5, bookHeight / 2); + ctx.lineTo(spineX + spineWidth / 2, bookHeight / 2 + pageLiftHeight); + ctx.closePath(); + ctx.fill(); + + // 添加翻起部分的阴影 + ctx.fillStyle = "rgba(0, 0, 0, 0.1)"; + ctx.beginPath(); + ctx.moveTo(spineX - spineWidth / 2, bookHeight / 2 - pageLiftHeight); + ctx.lineTo(spineX - spineWidth / 2 - 5, bookHeight / 2); + ctx.lineTo(spineX - spineWidth / 2 - 2, bookHeight / 2); + ctx.lineTo(spineX - spineWidth / 2, bookHeight / 2 - pageLiftHeight + 3); + ctx.closePath(); + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(spineX + spineWidth / 2, bookHeight / 2 - pageLiftHeight); + ctx.lineTo(spineX + spineWidth / 2 + 5, bookHeight / 2); + ctx.lineTo(spineX + spineWidth / 2 + 2, bookHeight / 2); + ctx.lineTo(spineX + spineWidth / 2, bookHeight / 2 - pageLiftHeight + 3); + ctx.closePath(); + ctx.fill(); + + // 4. 绘制书页 - 添加抗锯齿边缘处理 + // 先绘制一个作为书页底色 + ctx.fillStyle = '#cfbf96'; + ctx.beginPath(); + ctx.moveTo(dx, dy); + ctx.lineTo(dx + 2 * this.width, dy); + ctx.lineTo(dx + 2 * this.width, this.height + dy); + ctx.lineTo(dx, this.height + dy); + ctx.closePath(); + + ctx.fill(); + // 使用更精细的渐变填充 + // 创建更复杂的渐变效果 + const leftGradient = ctx.createLinearGradient(dx / 2, 0, dx, 0); + leftGradient.addColorStop(0, "#f0e8c8"); + leftGradient.addColorStop(0.3, "#d8c8a0"); + leftGradient.addColorStop(0.5, "#b09868"); + leftGradient.addColorStop(0.7, "#9e804a"); + leftGradient.addColorStop(1, "#8a6c3a"); + + // 绘制左侧封面 + ctx.beginPath(); + ctx.moveTo(dx, this.height + dy); + ctx.lineTo(dx / 2, this.height + dy); + ctx.lineTo(dx / 2, dy); + ctx.lineTo(dx, dy); + ctx.closePath(); + ctx.fillStyle = leftGradient; + ctx.fill(); + + // 添加竖向条纹效果(像素感破旧边缘) + + ctx.strokeStyle = "rgba(120, 90, 60, 0.3)"; + ctx.lineWidth = 1; + for (let i = 0; i < 10; i++) { + const x = dx / 2 + i * 2; + ctx.beginPath(); + ctx.moveTo(x, dy); + ctx.lineTo(x, dy + this.height); + ctx.stroke(); + } + + + // 添加不规则暗色区域(不使用随机数) + ctx.fillStyle = "rgba(90, 70, 40, 0.15)"; + ctx.beginPath(); + ctx.moveTo(dx / 2 + 10, dy + 50); + ctx.lineTo(dx / 2 + 30, dy + 40); + ctx.lineTo(dx / 2 + 25, dy + 100); + ctx.lineTo(dx / 2 + 15, dy + 120); + ctx.closePath(); + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(dx / 2 + 5, dy + 180); + ctx.lineTo(dx / 2 + 35, dy + 200); + ctx.lineTo(dx / 2 + 20, dy + 250); + ctx.lineTo(dx / 2, dy + 230); + ctx.closePath(); + ctx.fill(); + + // 右侧封面同理 + const rightGradient = ctx.createLinearGradient(676 - dx, 0, 676 - dx / 2, 0); + rightGradient.addColorStop(0, "#8a6c3a"); + rightGradient.addColorStop(0.3, "#9e804a"); + rightGradient.addColorStop(0.5, "#b09868"); + rightGradient.addColorStop(0.7, "#d8c8a0"); + rightGradient.addColorStop(1, "#f0e8c8"); + + ctx.beginPath(); + ctx.moveTo(676 - dx / 2, this.height + dy); + ctx.lineTo(676 - dx, this.height + dy); + ctx.lineTo(676 - dx, dy); + ctx.lineTo(676 - dx / 2, dy); + ctx.closePath(); + ctx.fillStyle = rightGradient; + ctx.fill(); + + // 添加右侧竖向条纹 + + ctx.strokeStyle = "rgba(120, 90, 60, 0.3)"; + ctx.lineWidth = 1; + for (let i = 0; i < 10; i++) { + const x = 676 - dx / 2 - i * 2; + ctx.beginPath(); + ctx.moveTo(x, dy); + ctx.lineTo(x, dy + this.height); + ctx.stroke(); + } + + + // 添加右侧不规则暗色区域 + ctx.fillStyle = "rgba(90, 70, 40, 0.15)"; + ctx.beginPath(); + ctx.moveTo(676 - dx / 2 - 10, dy + 70); + ctx.lineTo(676 - dx / 2 - 25, dy + 80); + ctx.lineTo(676 - dx / 2 - 20, dy + 140); + ctx.lineTo(676 - dx / 2 - 5, dy + 130); + ctx.closePath(); + ctx.fill(); + // 添加书页边缘老化渐变效果 + // 创建边缘渐变遮罩 + const edgeGradient = ctx.createRadialGradient( + dx + this.width / 2, this.height / 2, 5, // 内圆 + dx + this.width / 2, this.height / 2, Math.max(this.width, this.height) / 2 // 外圆 + ); + edgeGradient.addColorStop(0, "rgba(0,0,0,0)"); // 中心透明 + edgeGradient.addColorStop(0.7, "rgba(0,0,0,0)"); // 中间部分透明 + edgeGradient.addColorStop(1, "rgba(50,40,20,0.15)"); // 边缘轻微老化效果 + + // 应用边缘渐变 + ctx.fillStyle = edgeGradient; + ctx.globalCompositeOperation = 'multiply'; // 使用乘法混合模式使边缘变暗 + ctx.beginPath(); + ctx.moveTo(dx, dy); + ctx.lineTo(dx + 2 * this.width, dy); + ctx.lineTo(dx + 2 * this.width, this.height + dy); + ctx.lineTo(dx, this.height + dy); + ctx.closePath(); + ctx.fill(); + ctx.globalCompositeOperation = 'source-over'; // 恢复默认混合模式 + + // 进一步添加边缘老化效果 - 左右边框 + const sideEdgeGradientLeft = ctx.createLinearGradient(dx, 0, dx + this.width, 0); + sideEdgeGradientLeft.addColorStop(0, "rgba(90,70,50,0.15)"); + sideEdgeGradientLeft.addColorStop(0.1, "rgba(90,70,50,0.07)"); + sideEdgeGradientLeft.addColorStop(0.9, "rgba(90,70,50,0.07)"); + sideEdgeGradientLeft.addColorStop(1, "rgba(90,70,50,0.15)"); + + ctx.fillStyle = sideEdgeGradientLeft; + ctx.fillRect(dx, 0, this.width, this.height); + + const sideEdgeGradientRight = ctx.createLinearGradient(dx + this.width, 0, dx, 0); + sideEdgeGradientRight.addColorStop(0, "rgba(90,70,50,0.15)"); + sideEdgeGradientRight.addColorStop(0.1, "rgba(90,70,50,0.07)"); + sideEdgeGradientRight.addColorStop(0.9, "rgba(90,70,50,0.07)"); + sideEdgeGradientRight.addColorStop(1, "rgba(90,70,50,0.15)"); + ctx.fillStyle = sideEdgeGradientRight; + ctx.fillRect(dx, 0, this.width, this.height); + + // 上下边缘老化效果 + const topEdgeGradient = ctx.createLinearGradient(0, dy, 0, dy + this.height); + topEdgeGradient.addColorStop(0, "rgba(90,70,50,0.15)"); + topEdgeGradient.addColorStop(0.1, "rgba(90,70,50,0.07)"); + topEdgeGradient.addColorStop(0.9, "rgba(90,70,50,0.07)"); + topEdgeGradient.addColorStop(1, "rgba(90,70,50,0.15)"); + + ctx.fillStyle = topEdgeGradient; + ctx.fillRect(dx, dy, this.width, this.height); + + const bottomEdgeGradient = ctx.createLinearGradient(0, dy + this.height, 0, dy); + bottomEdgeGradient.addColorStop(0, "rgba(90,70,50,0.15)"); + bottomEdgeGradient.addColorStop(0.1, "rgba(90,70,50,0.07)"); + bottomEdgeGradient.addColorStop(0.9, "rgba(90,70,50,0.07)"); + bottomEdgeGradient.addColorStop(1, "rgba(90,70,50,0.15)"); + + ctx.fillStyle = bottomEdgeGradient; + ctx.fillRect(dx, dy, this.width, this.height); + // 使用阴影模糊来柔化边缘 + ctx.shadowColor = "rgba(0,0,0,0.5)"; + ctx.shadowBlur = 10; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + + // 绘制左页 + core.drawImage(ctx, this.paperpages[this.page - 1][0], 0, 0, 300, 400, dx, dy, this.width, this.height); + + // 绘制右页 + core.drawImage(ctx, this.paperpages[this.page - 1][1], 0, 0, 300, 400, dx + this.width, dy, this.width, this.height); + + // 重置阴影 + ctx.shadowColor = "transparent"; + + + ctx.restore(); } + setPage(num) { + const a = [] + for (let i = 0; i < num; i++) { + a.push([this.paperTexture('left'), this.paperTexture('right')]) + } + this.paperpages = a + } + clearPage() { + this.paperpages.forEach(v => { + if (v[0].parentNode) { + // 从 DOM 树中删除节点,除非它已经被删除了。 + v[0].parentNode.removeChild(v[0]); + } + if (v[1].parentNode) { + // 从 DOM 树中删除节点,除非它已经被删除了。 + v[1].parentNode.removeChild(v[1]); + } + + }) + this.paperpages = [] + } + update() { + if (this.paperpages.length === 0) this.paperpages.push([this.paperTexture('left'), this.paperTexture('right')]) + if (this.pagemax < 1) this.pagemax = 1 + if (this.page < 1) this.page = 1 + this.background() + } + + } core.book = new Book() + }, "存读档": function () { // 在此增加新插件