diff --git a/project/plugins.js b/project/plugins.js index bddace9..7d79f4c 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -8042,879 +8042,880 @@ let time=0 core.registerReplayAction("skill", control.prototype._replayAction_skill); }, "animate": function () { - // -------------------- 插件说明 -------------------- // + // -------------------- 插件说明 -------------------- // - // github仓库:https://github.com/unanmed/animate - // npm包名:mutate-animate - // npm地址:https://www.npmjs.com/package/mutate-animate + // github仓库:https://github.com/unanmed/animate + // npm包名:mutate-animate + // npm地址:https://www.npmjs.com/package/mutate-animate - // 不要去尝试读这个插件,这个插件是经过了打包的,不是人类可读的( - // 想读的话可以去github读 + // 不要去尝试读这个插件,这个插件是经过了打包的,不是人类可读的( + // 想读的话可以去github读 - // 该插件是一个轻量型多功能动画插件,可以允许你使用内置或自定义的速率曲线或轨迹等 - // 除此之外,你还可以自定义绘制函数,来让你的动画可视化 + // 该插件是一个轻量型多功能动画插件,可以允许你使用内置或自定义的速率曲线或轨迹等 + // 除此之外,你还可以自定义绘制函数,来让你的动画可视化 - // -------------------- 安装说明 -------------------- // + // -------------------- 安装说明 -------------------- // - // 直接复制到插件中即可,注意所有插件中不能出现插件名为animate的插件 - // 该插件分为动画和渐变两部分,教程分开,动画在前,渐变在后 + // 直接复制到插件中即可,注意所有插件中不能出现插件名为animate的插件 + // 该插件分为动画和渐变两部分,教程分开,动画在前,渐变在后 - // -------------------- 动画使用教程 -------------------- // + // -------------------- 动画使用教程 -------------------- // - // 1. 首先创建一个异步函数 - // async function ani() { } + // 1. 首先创建一个异步函数 + // async function ani() { } - // 2. 引入插件中的类和函数,引入内容要看个人需求,所有可用的函数在本插件末尾可以看到 - // const { Animation, linear, bezier, circle, hyper, trigo, power, inverseTrigo, shake, sleep } = core.plugin.animate + // 2. 引入插件中的类和函数,引入内容要看个人需求,所有可用的函数在本插件末尾可以看到 + // const { Animation, linear, bezier, circle, hyper, trigo, power, inverseTrigo, shake, sleep } = core.plugin.animate - // 3. 在函数内部创建一个动画 - // const animate = new Animation(); + // 3. 在函数内部创建一个动画 + // const animate = new Animation(); - // 4. 为动画创建一个绘制函数,这里以绘制一个矩形为例,当然也可以使用core.fillRect替代ctx.fillRect来绘制矩形 - // const ctx = core.createCanvas('animate', 0, 0, 416, 416, 100); - // ctx.save(); - // const fn = () => { - // ctx.restore(); - // ctx.save(); - // ctx.clearRect(0, 0, 800, 800); - // ctx.translate(animate.x, animate.y); - // ctx.rotate(animate.angle * Math.PI / 180); - // const size = animate.size; - // ctx.fillRect(-30 * size, -30 * size, 60 * size, 60 * size); - // } - // animate.ticker.add(fn); + // 4. 为动画创建一个绘制函数,这里以绘制一个矩形为例,当然也可以使用core.fillRect替代ctx.fillRect来绘制矩形 + // const ctx = core.createCanvas('animate', 0, 0, 416, 416, 100); + // ctx.save(); + // const fn = () => { + // ctx.restore(); + // ctx.save(); + // ctx.clearRect(0, 0, 800, 800); + // ctx.translate(animate.x, animate.y); + // ctx.rotate(animate.angle * Math.PI / 180); + // const size = animate.size; + // ctx.fillRect(-30 * size, -30 * size, 60 * size, 60 * size); + // } + // animate.ticker.add(fn); - // 5. 执行动画 + // 5. 执行动画 - // 下面先对一些概念进行解释 + // 下面先对一些概念进行解释 - // 动画分为很多种,内置的有move(移动至某一点) rotate(旋转) scale(放缩) moveAs(以指定路径移动) shake(震动) - // 对于不同的动画种类,其所对应的属性也不同,move moveAs shake均对应x和y这两个属性 - // rotate对应angle,scale对应size。你也可以自定义属性,这个之后会提到 + // 动画分为很多种,内置的有move(移动至某一点) rotate(旋转) scale(放缩) moveAs(以指定路径移动) shake(震动) + // 对于不同的动画种类,其所对应的属性也不同,move moveAs shake均对应x和y这两个属性 + // rotate对应angle,scale对应size。你也可以自定义属性,这个之后会提到 - // 除了执行动画之外,这里还提供了三个等待函数,可以等待某个动画执行完毕,以及一个等待指定时长的函数 - // 分别是animate.n(等待指定数量的动画执行完毕) - // animate.w(等待指定类型的动画执行完毕,也可以是自定义类型) - // animate.all(等待所有动画执行完毕) - // sleep(等待指定时长) + // 除了执行动画之外,这里还提供了三个等待函数,可以等待某个动画执行完毕,以及一个等待指定时长的函数 + // 分别是animate.n(等待指定数量的动画执行完毕) + // animate.w(等待指定类型的动画执行完毕,也可以是自定义类型) + // animate.all(等待所有动画执行完毕) + // sleep(等待指定时长) - // 执行动画时,要求一个渐变函数,当然这个插件内置了非常丰富的渐变函数,也就是速率曲线。 + // 执行动画时,要求一个渐变函数,当然这个插件内置了非常丰富的渐变函数,也就是速率曲线。 - // 线性渐变函数 linear(),该函数返回一个线性变化函数 + // 线性渐变函数 linear(),该函数返回一个线性变化函数 - // 三角渐变函数 trigo('sin' | 'sec', EaseMode),该函数返回一个指定属性的三角函数变化函数 - // 其中EaseMode可以填'in' 'out' 'in-out' 'center' - // 分别表示 慢-快 快-慢 慢-快-慢 快-慢-快 + // 三角渐变函数 trigo('sin' | 'sec', EaseMode),该函数返回一个指定属性的三角函数变化函数 + // 其中EaseMode可以填'in' 'out' 'in-out' 'center' + // 分别表示 慢-快 快-慢 慢-快-慢 快-慢-快 - // 幂函数渐变 power(n, EaseMode),该函数返回一个以x^n变化的函数,n是指数 + // 幂函数渐变 power(n, EaseMode),该函数返回一个以x^n变化的函数,n是指数 - // 双曲渐变函数 hyper('sin' | 'tan' | 'sec', EaseMode),该函数返回一个双曲函数,分别是双曲正弦、双曲正切、双曲正割 + // 双曲渐变函数 hyper('sin' | 'tan' | 'sec', EaseMode),该函数返回一个双曲函数,分别是双曲正弦、双曲正切、双曲正割 - // 反三角渐变函数 inverseTrigo('sin' | 'tan', EaseMode),该函数返回一个反三角函数 + // 反三角渐变函数 inverseTrigo('sin' | 'tan', EaseMode),该函数返回一个反三角函数 - // 贝塞尔曲线渐变函数 bezier(...cps),参数为贝塞尔曲线的控制点纵坐标(横坐标不能自定义,毕竟一个时刻不能对应多个速率) - // 示例:bezier(0.4, 0.2, 0.7); // 三个控制点的四次贝塞尔曲线渐变函数 + // 贝塞尔曲线渐变函数 bezier(...cps),参数为贝塞尔曲线的控制点纵坐标(横坐标不能自定义,毕竟一个时刻不能对应多个速率) + // 示例:bezier(0.4, 0.2, 0.7); // 三个控制点的四次贝塞尔曲线渐变函数 - // 了解完渐变函数以后,这里还有一个特殊的渐变函数-shake - // shake(power, timing),这个函数是一个震荡函数,会让一个值来回变化,实现震动的效果 - // 其中power是震动的最大值,timing是渐变函数,描述了power在震动时大小的变化 + // 了解完渐变函数以后,这里还有一个特殊的渐变函数-shake + // shake(power, timing),这个函数是一个震荡函数,会让一个值来回变化,实现震动的效果 + // 其中power是震动的最大值,timing是渐变函数,描述了power在震动时大小的变化 - // 下面,我们就可以进行动画的执行了,我们以 运动 + 旋转 + 放缩为例 + // 下面,我们就可以进行动画的执行了,我们以 运动 + 旋转 + 放缩为例 - // animate.mode(hyper('sin', 'out')) // 设置渐变函数为 双曲正弦 快 -> 慢,注意不能加分号 - // .time(1000) // 设置动画的执行时间为1000毫秒 - // .move(300, 300) // 移动至[300, 300]的位置 - // .relative() // 设置相对模式为相对之前,与之前为相加的关系 - // .mode(power(3, 'center')) // 设置为 x^3 快-慢-快 的渐变函数 - // .time(3000) - // .rotate(720) // 旋转720度 - // .absolute() // 设置相对模式为绝对 - // .mode(trigo('sin', 'in')) // 设置渐变函数为 正弦 慢 -> 快 - // .time(1500) - // .scale(3); // 放缩大小至3倍 + // animate.mode(hyper('sin', 'out')) // 设置渐变函数为 双曲正弦 快 -> 慢,注意不能加分号 + // .time(1000) // 设置动画的执行时间为1000毫秒 + // .move(300, 300) // 移动至[300, 300]的位置 + // .relative() // 设置相对模式为相对之前,与之前为相加的关系 + // .mode(power(3, 'center')) // 设置为 x^3 快-慢-快 的渐变函数 + // .time(3000) + // .rotate(720) // 旋转720度 + // .absolute() // 设置相对模式为绝对 + // .mode(trigo('sin', 'in')) // 设置渐变函数为 正弦 慢 -> 快 + // .time(1500) + // .scale(3); // 放缩大小至3倍 - // 这样,我们就把三种基础动画都执行了一遍,同时,这种写法非常直观,出现问题时也可以很快地找到问题所在 - // 下面,我们需要等待动画执行完毕,因为同一种动画不可能同时执行两个 + // 这样,我们就把三种基础动画都执行了一遍,同时,这种写法非常直观,出现问题时也可以很快地找到问题所在 + // 下面,我们需要等待动画执行完毕,因为同一种动画不可能同时执行两个 - // await animate.n(1); // 等待任意一个动画执行完毕,别把await忘了 - // await animate.w('scale'); // 等待放缩动画执行完毕 - // await animate.all(); // 等待所有动画执行完毕 - // await sleep(1000); // 等待1000毫秒 + // await animate.n(1); // 等待任意一个动画执行完毕,别把await忘了 + // await animate.w('scale'); // 等待放缩动画执行完毕 + // await animate.all(); // 等待所有动画执行完毕 + // await sleep(1000); // 等待1000毫秒 - // 下面,还有一个特殊的动画函数-moveAs - // 这是一个非常强大的函数,它允许你让你的物体按照指定路线运动 - // 说到这,我们需要先了解一下运动函数。 - // 该插件内置了两个运动函数,分别是圆形运动和贝塞尔曲线运动 + // 下面,还有一个特殊的动画函数-moveAs + // 这是一个非常强大的函数,它允许你让你的物体按照指定路线运动 + // 说到这,我们需要先了解一下运动函数。 + // 该插件内置了两个运动函数,分别是圆形运动和贝塞尔曲线运动 - // 圆形运动 circle(r, n, timing, inverse),r是圆的半径,n是圈数,timing描述半径大小的变化,inverse说明了是否翻转timing函数,后面三个可以不填 + // 圆形运动 circle(r, n, timing, inverse),r是圆的半径,n是圈数,timing描述半径大小的变化,inverse说明了是否翻转timing函数,后面三个可以不填 - // 贝塞尔曲线 bezierPath(start, end, ...cps) - // 其中start和end是起点和结束点,应当填入[x, y]数组,cps是控制点,也是[x, y]数组 - // 示例:bezierPath([0, 0], [200, 200], [100, 50], [300, 150], [200, 180]); - // 这是一个起点为 [0, 0],终点为[200, 200],有三个控制点的四次贝塞尔曲线 + // 贝塞尔曲线 bezierPath(start, end, ...cps) + // 其中start和end是起点和结束点,应当填入[x, y]数组,cps是控制点,也是[x, y]数组 + // 示例:bezierPath([0, 0], [200, 200], [100, 50], [300, 150], [200, 180]); + // 这是一个起点为 [0, 0],终点为[200, 200],有三个控制点的四次贝塞尔曲线 - // 下面,我们就可以使用路径函数了 + // 下面,我们就可以使用路径函数了 - // animate.mode(hyper('sin', 'in-out')) // 设置渐变曲线 - // .time(5000) - // .relative() // 设置为相对模式,这个比较必要,不然的话很可能出现瞬移 - // .moveAs(circle(100, 5, linear())) // 创建一个5圈的半径从0至100逐渐变大的圆轨迹(是个螺旋线)并让物体沿着它运动 - // - // 最后,还有一个震动函数 shake(x, y),x和y表示了在横向上和纵向上的震动幅度,1表示为震动幅度的100% - // 示例: - // animate.mode(shake(5, hyper('sin', 'in')), true) // 这里第二个参数说明是震动函数 - // .time(2000) - // .shake(1, 0.5) + // animate.mode(hyper('sin', 'in-out')) // 设置渐变曲线 + // .time(5000) + // .relative() // 设置为相对模式,这个比较必要,不然的话很可能出现瞬移 + // .moveAs(circle(100, 5, linear())) // 创建一个5圈的半径从0至100逐渐变大的圆轨迹(是个螺旋线)并让物体沿着它运动 + // + // 最后,还有一个震动函数 shake(x, y),x和y表示了在横向上和纵向上的震动幅度,1表示为震动幅度的100% + // 示例: + // animate.mode(shake(5, hyper('sin', 'in')), true) // 这里第二个参数说明是震动函数 + // .time(2000) + // .shake(1, 0.5) - // 这样,所有内置动画就已经介绍完毕 + // 这样,所有内置动画就已经介绍完毕 - // 6. 自定义动画属性 + // 6. 自定义动画属性 - // 本插件允许你自定义一个动画属性,但功能可能不会像自带的属性那么强大 - // 你可以在创建动画之后使用animate.register(key, init)来注册一个自定义属性 - // 其中key是自定义属性的名称,init是自定义属性的初始值,这个值应当在0-1之间变化 + // 本插件允许你自定义一个动画属性,但功能可能不会像自带的属性那么强大 + // 你可以在创建动画之后使用animate.register(key, init)来注册一个自定义属性 + // 其中key是自定义属性的名称,init是自定义属性的初始值,这个值应当在0-1之间变化 - // 你可以通过animate.value[key]来获取你注册的自定义属性 + // 你可以通过animate.value[key]来获取你注册的自定义属性 - // 对于自定义属性的动画,你应当使用animate.apply(key, n, first) - // 其中,key是你的自定义属性的名称,n是其目标值,first是一个布尔值,说明了是否将该动画插入到目前所有的动画之前,即每帧会优先执行该动画 + // 对于自定义属性的动画,你应当使用animate.apply(key, n, first) + // 其中,key是你的自定义属性的名称,n是其目标值,first是一个布尔值,说明了是否将该动画插入到目前所有的动画之前,即每帧会优先执行该动画 - // 下面是一个不透明度的示例 + // 下面是一个不透明度的示例 - // animate.register('opacity', 1); // 这句话应该放到刚创建动画之后 + // animate.register('opacity', 1); // 这句话应该放到刚创建动画之后 - // ctx.globalAlpha = animate.value.opacity; // 这句话应当放到每帧绘制的函数里面,放在绘制之前 + // ctx.globalAlpha = animate.value.opacity; // 这句话应当放到每帧绘制的函数里面,放在绘制之前 - // animate.mode(bezier(0.9, 0.1, 0.05)) // 设置渐变函数 - // .time(2000) - // .absolute() - // .apply('opacity', 0.3); // 将不透明度按照渐变曲线更改为0.3 + // animate.mode(bezier(0.9, 0.1, 0.05)) // 设置渐变函数 + // .time(2000) + // .absolute() + // .apply('opacity', 0.3); // 将不透明度按照渐变曲线更改为0.3 - // 7. 运行动画 + // 7. 运行动画 - // 还记得刚开始定义的async function 吗,直接调用它就能执行动画了! - // 示例:ani(); // 执行刚刚写的所有动画 - - // 8. 自定义速率曲线和路径 - - // 该插件中,速率曲线和路径均可自定义 - - // 对于速率曲线,其类型为 (input: number) => number - // 它接受一个范围在 0-1 的值,输出一个 0-1 的值,表示了动画的完成度,1表示动画已完成,0表示动画刚开始(当前大于1小于0也不会报错,也会执行相应的动画) - - // 对于路径,其类型为 (input: number) => [number, number] - // 它与速率曲线类似,接收一个 0-1 的值,输出一个坐标数组 - - // 9. 多个属性绑定 - - // 该插件中,你可以绑定多个动画属性,你可以使用ani.bind(...attr)来绑定。 - // 绑定之后,这三个动画属性可以被一个返回了长度为3的数组的渐变函数执行。 - // 绑定使用ani.bind,设置渐变函数仍然使用ani.mode,注意它与单个动画属性是分开的,也就是它不会影响正常的渐变函数。 - // 然后使用ani.applyMulti即可执行动画 - - // 例如: - // // 自定义的一个三属性渐变函数 - // function b(input) { - // return [input * 100, input ** 2 * 100, input ** 3 * 100]; - // } - // ani.bind('a', 'b', 'c') // 这样会绑定abc这三个动画属性 - // .mode(b) // 自定义的一个返回了长度为3的数组的函数 - // .time(5000) - // .absolute() - // .applyMulti(); // 执行这个动画 - - // 9. 监听 动画的生命周期钩子 - - // 这个插件还允许你去监听动画的状态,可以监听动画的开始、结束、运行 - // 你可以使用 animate.listen(type, fn)来监听,fn的类型是 (a: Animation, type: string) => void - // 当然,一般情况下你不会用到这个功能,插件中已经帮你包装了三个等待函数,他们就是以这些监听为基础的 - - // 10. 自定义时间获取函数 - - // 你可以修改ani.getTime来修改动画的时间获取函数,例如想让动画速度变成一半可以写ani.getTime = () => Date.now() / 2 - // 这样可以允许你随意控制动画的运行速度,暂停,甚至是倒退。该值默认为`Date.now` - - // -------------------- 渐变使用教程 -------------------- // - - // 相比于动画,渐变属于一种较为简便的动画,它可以让你在设置一个属性后使属性缓慢变化值目标值而不是突变至目标值 - // 现在假设你已经了解了动画的使用,下面我们来了解渐变。 - - // 1. 创建一个渐变实例 - // 与动画类似,你需要使用new来实例化一个渐变,当然别忘了引入 - // const { Transition } = core.plugin.animate; - // const tran = new Transition(); - - // 2. 绘制 - // const ctx = core.createCanvas('transition', 0, 0, 416, 416, 100); - // ctx.save(); - // const fn = () => { - // ctx.restore(); - // ctx.save(); - // ctx.clearRect(0, 0, 800, 800); - // ctx.beginPath(); - // ctx.arc(tran.value.x, tran.value.y, 50, 0, Math.PI * 2); // 使用tran.value.xxx获取当前的属性 - // ctx.fill(); - // // 当然也可以用样板的api,例如core.fillCircle();等 - // } - // animate.ticker.add(fn); - - // 3. 设置渐变 - // 同样,与动画类似,你可以使用tran.time()设置渐变时间,使用tran.mode()设置渐变函数,使用tran.absolute()和tran.relative()设置相对模式 - // 例如: - // tran.time(1000) - // .mode(hyper('sin', 'out')) - // .absolute(); - - // 4. 初始化渐变属性 - // 与动画不同的是,动画在执行一个自定义属性前都需要register,而渐变不需要。 - // 你可以通过tran.value.xxx = yyy来设置动画属性或使用tran.transition('xxx', yyy)来设置 - // 你的首次赋值即是初始化了渐变属性,这时是不会执行渐变的,例如: - // tran.value.x = 200; - // tran.transition('y', 200); - // 上述例子便是将 x 和 y 初始化成了200 - - // 5. 执行渐变 - // 初始化完成后,便可以直接执行渐变了,有两种方法 - // tran.value.x = 400; // 将 x 缓慢移动至400 - // tran.transition('y', 400); // 将 y 缓慢移动至400 - - // 6. 自定义时间获取函数 - // 与动画类似,你依然可以通过修改tran.getTime来修改时间获取函数 - - if (main.replayChecking) return (core.plugin.animate = {}); - - var M = Object.defineProperty; - var E = (n, i, t) => - i in n - ? M(n, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) - : (n[i] = t); - var o = (n, i, t) => (E(n, typeof i != "symbol" ? i + "" : i, t), t); - let w = []; - const k = (n) => { - for (const i of w) - if (i.status === "running") - try { - for (const t of i.funcs) t(n - i.startTime); - } catch (t) { - i.destroy(), console.error(t); - } - requestAnimationFrame(k); - }; - requestAnimationFrame(k); - class I { - constructor() { - o(this, "funcs", /* @__PURE__ */ new Set()); - o(this, "status", "stop"); - o(this, "startTime", 0); - (this.status = "running"), - w.push(this), - requestAnimationFrame((i) => (this.startTime = i)); - } - add(i) { - return this.funcs.add(i), this; - } - remove(i) { - return this.funcs.delete(i), this; - } - clear() { - this.funcs.clear(); - } - destroy() { - this.clear(), this.stop(); - } - stop() { - (this.status = "stop"), (w = w.filter((i) => i !== this)); - } - } - class F { - constructor() { - o(this, "timing"); - o(this, "relation", "absolute"); - o(this, "easeTime", 0); - o(this, "applying", {}); - o(this, "getTime", Date.now); - o(this, "ticker", new I()); - o(this, "value", {}); - o(this, "listener", {}); - this.timing = (i) => i; - } - async all() { - if (Object.values(this.applying).every((i) => i === !0)) - throw new ReferenceError("There is no animates to be waited."); - await new Promise((i) => { - const t = () => { - Object.values(this.applying).every((e) => e === !1) && - (this.unlisten("end", t), i("all animated.")); - }; - this.listen("end", t); - }); - } - async n(i) { - const t = Object.values(this.applying).filter((s) => s === !0).length; - if (t < i) - throw new ReferenceError( - `You are trying to wait ${i} animate, but there are only ${t} animate animating.` - ); - let e = 0; - await new Promise((s) => { - const r = () => { - e++, e === i && (this.unlisten("end", r), s(`${i} animated.`)); - }; - this.listen("end", r); - }); - } - async w(i) { - if (this.applying[i] === !1) - throw new ReferenceError(`The ${i} animate is not animating.`); - await new Promise((t) => { - const e = () => { - this.applying[i] === !1 && - (this.unlisten("end", e), t(`${i} animated.`)); - }; - this.listen("end", e); - }); - } - listen(i, t) { - var e, s; - (s = (e = this.listener)[i]) != null || (e[i] = []), - this.listener[i].push(t); - } - unlisten(i, t) { - const e = this.listener[i].findIndex((s) => s === t); - if (e === -1) - throw new ReferenceError( - "You are trying to remove a nonexistent listener." - ); - this.listener[i].splice(e, 1); - } - hook(...i) { - const t = Object.entries(this.listener).filter((e) => i.includes(e[0])); - for (const [e, s] of t) for (const r of s) r(this, e); - } - } - - function y(n) { - return n != null; - } - async function R(n) { - return new Promise((i) => setTimeout(i, n)); - } - class j extends F { - constructor() { - super(); - o(this, "shakeTiming"); - o(this, "path"); - o(this, "multiTiming"); - o(this, "value", {}); - o(this, "size", 1); - o(this, "angle", 0); - o(this, "targetValue", { - system: { - move: [0, 0], - moveAs: [0, 0], - resize: 0, - rotate: 0, - shake: 0, - "@@bind": [], - }, - custom: {}, - }); - o(this, "animateFn", { - system: { - move: [() => 0, () => 0], - moveAs: () => 0, - resize: () => 0, - rotate: () => 0, - shake: () => 0, - "@@bind": () => 0, - }, - custom: {}, - }); - o(this, "ox", 0); - o(this, "oy", 0); - o(this, "sx", 0); - o(this, "sy", 0); - o(this, "bindInfo", []); - (this.timing = (t) => t), - (this.shakeTiming = (t) => t), - (this.multiTiming = (t) => [t, t]), - (this.path = (t) => [t, t]), - (this.applying = { - move: !1, - scale: !1, - rotate: !1, - shake: !1, - }), - this.ticker.add(() => { - const { running: t } = this.listener; - if (y(t)) for (const e of t) e(this, "running"); - }); - } - get x() { - return this.ox + this.sx; - } - get y() { - return this.oy + this.sy; - } - mode(t, e = !1) { - return ( - typeof t(0) == "number" - ? e - ? (this.shakeTiming = t) - : (this.timing = t) - : (this.multiTiming = t), - this - ); - } - time(t) { - return (this.easeTime = t), this; - } - relative() { - return (this.relation = "relative"), this; - } - absolute() { - return (this.relation = "absolute"), this; - } - bind(...t) { - return ( - this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), - (this.bindInfo = t), - this - ); - } - unbind() { - return ( - this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), - (this.bindInfo = []), - this - ); - } - move(t, e) { - return ( - this.applying.move && this.end(!0, "move"), - this.applySys("ox", t, "move"), - this.applySys("oy", e, "move"), - this - ); - } - rotate(t) { - return this.applySys("angle", t, "rotate"), this; - } - scale(t) { - return this.applySys("size", t, "resize"), this; - } - shake(t, e) { - this.applying.shake === !0 && this.end(!0, "shake"), - (this.applying.shake = !0); - const { easeTime: s, shakeTiming: r } = this, - l = this.getTime(); - if ((this.hook("start", "shakestart"), s <= 0)) - return this.end(!1, "shake"), this; - const a = () => { - const c = this.getTime() - l; - if (c > s) { - this.ticker.remove(a), - (this.applying.shake = !1), - (this.sx = 0), - (this.sy = 0), - this.hook("end", "shakeend"); - return; - } - const h = c / s, - m = r(h); - (this.sx = m * t), (this.sy = m * e); - }; - return this.ticker.add(a), (this.animateFn.system.shake = a), this; - } - moveAs(t) { - this.applying.moveAs && this.end(!0, "moveAs"), - (this.applying.moveAs = !0), - (this.path = t); - const { easeTime: e, relation: s, timing: r } = this, - l = this.getTime(), - [a, u] = [this.x, this.y], - [c, h] = (() => { - if (s === "absolute") return t(1); - { - const [d, f] = t(1); - return [a + d, u + f]; - } - })(); - if ((this.hook("start", "movestart"), e <= 0)) - return this.end(!1, "moveAs"), this; - const m = () => { - const f = this.getTime() - l; - if (f > e) { - this.end(!0, "moveAs"); - return; - } - const g = f / e, - [v, x] = t(r(g)); - s === "absolute" - ? ((this.ox = v), (this.oy = x)) - : ((this.ox = a + v), (this.oy = u + x)); - }; - return ( - this.ticker.add(m), - (this.animateFn.system.moveAs = m), - (this.targetValue.system.moveAs = [c, h]), - this - ); - } - register(t, e) { - if (typeof this.value[t] == "number") - return this.error( - `Property ${t} has been regietered twice.`, - "reregister" - ); - (this.value[t] = e), (this.applying[t] = !1); - } - apply(t, e) { - this.applying[t] === !0 && this.end(!1, t), - t in this.value || - this.error(`You are trying to execute nonexistent property ${t}.`), - (this.applying[t] = !0); - const s = this.value[t], - r = this.getTime(), - { timing: l, relation: a, easeTime: u } = this, - c = a === "absolute" ? e - s : e; - if ((this.hook("start"), u <= 0)) return this.end(!1, t), this; - const h = () => { - const d = this.getTime() - r; - if (d > u) { - this.end(!1, t); - return; - } - const f = d / u, - g = l(f); - this.value[t] = s + g * c; - }; - return ( - this.ticker.add(h), - (this.animateFn.custom[t] = h), - (this.targetValue.custom[t] = c + s), - this - ); - } - applyMulti() { - this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), - (this.applying["@@bind"] = !0); - const t = this.bindInfo, - e = t.map((h) => this.value[h]), - s = this.getTime(), - { multiTiming: r, relation: l, easeTime: a } = this, - u = r(1); - if (u.length !== e.length) - throw new TypeError( - `The number of binded animate attributes and timing function returns's length does not match. binded: ${t.length}, timing: ${u.length}` - ); - if ((this.hook("start"), a <= 0)) return this.end(!1, "@@bind"), this; - const c = () => { - const m = this.getTime() - s; - if (m > a) { - this.end(!1, "@@bind"); - return; - } - const d = m / a, - f = r(d); - t.forEach((g, v) => { - l === "absolute" - ? (this.value[g] = f[v]) - : (this.value[g] = e[v] + f[v]); - }); - }; - return ( - this.ticker.add(c), - (this.animateFn.custom["@@bind"] = c), - (this.targetValue.system["@@bind"] = u), - this - ); - } - applySys(t, e, s) { - s !== "move" && this.applying[s] === !0 && this.end(!0, s), - (this.applying[s] = !0); - const r = this[t], - l = this.getTime(), - a = this.timing, - u = this.relation, - c = this.easeTime, - h = u === "absolute" ? e - r : e; - if ((this.hook("start", `${s}start`), c <= 0)) return this.end(!0, s); - const m = () => { - const f = this.getTime() - l; - if (f > c) { - this.end(!0, s); - return; - } - const g = f / c, - v = a(g); - (this[t] = r + h * v), t !== "oy" && this.hook(s); - }; - this.ticker.add(m), - t === "ox" - ? (this.animateFn.system.move[0] = m) - : t === "oy" - ? (this.animateFn.system.move[1] = m) - : (this.animateFn.system[s] = m), - s === "move" - ? (t === "ox" && (this.targetValue.system.move[0] = h + r), - t === "oy" && (this.targetValue.system.move[1] = h + r)) - : s !== "shake" && (this.targetValue.system[s] = h + r); - } - error(t, e) { - throw e === "repeat" - ? new Error(`Cannot execute the same animation twice. Info: ${t}`) - : e === "reregister" - ? new Error(`Cannot register an animated property twice. Info: ${t}`) - : new Error(t); - } - end(t, e) { - if (t === !0) - if ( - ((this.applying[e] = !1), - e === "move" - ? (this.ticker.remove(this.animateFn.system.move[0]), - this.ticker.remove(this.animateFn.system.move[1])) - : e === "moveAs" - ? this.ticker.remove(this.animateFn.system.moveAs) - : e === "@@bind" - ? this.ticker.remove(this.animateFn.system["@@bind"]) - : this.ticker.remove(this.animateFn.system[e]), - e === "move") - ) { - const [s, r] = this.targetValue.system.move; - (this.ox = s), (this.oy = r), this.hook("moveend", "end"); - } else if (e === "moveAs") { - const [s, r] = this.targetValue.system.moveAs; - (this.ox = s), (this.oy = r), this.hook("moveend", "end"); - } else - e === "rotate" - ? ((this.angle = this.targetValue.system.rotate), - this.hook("rotateend", "end")) - : e === "resize" - ? ((this.size = this.targetValue.system.resize), - this.hook("resizeend", "end")) - : e === "@@bind" - ? this.bindInfo.forEach((r, l) => { - this.value[r] = this.targetValue.system["@@bind"][l]; - }) - : ((this.sx = 0), (this.sy = 0), this.hook("shakeend", "end")); - else - (this.applying[e] = !1), - this.ticker.remove(this.animateFn.custom[e]), - (this.value[e] = this.targetValue.custom[e]), - this.hook("end"); - } - } - class O extends F { - constructor() { - super(); - o(this, "now", {}); - o(this, "target", {}); - o(this, "transitionFn", {}); - o(this, "value"); - o(this, "handleSet", (t, e, s) => (this.transition(e, s), !0)); - o(this, "handleGet", (t, e) => this.now[e]); - (this.timing = (t) => t), - (this.value = new Proxy(this.target, { - set: this.handleSet, - get: this.handleGet, - })); - } - mode(t) { - return (this.timing = t), this; - } - time(t) { - return (this.easeTime = t), this; - } - relative() { - return (this.relation = "relative"), this; - } - absolute() { - return (this.relation = "absolute"), this; - } - transition(t, e) { - if (e === this.target[t]) return this; - if (!y(this.now[t])) return (this.now[t] = e), this; - this.applying[t] && this.end(t, !0), - (this.applying[t] = !0), - this.hook("start"); - const s = this.getTime(), - r = this.easeTime, - l = this.timing, - a = this.now[t], - u = e + (this.relation === "absolute" ? 0 : a), - c = u - a; - this.target[t] = u; - const h = () => { - const d = this.getTime() - s; - if (d >= r) { - this.end(t); - return; - } - const f = d / r; - (this.now[t] = l(f) * c + a), this.hook("running"); - }; - return ( - (this.transitionFn[t] = h), - this.ticker.add(h), - r <= 0 ? (this.end(t), this) : this - ); - } - end(t, e = !1) { - const s = this.transitionFn[t]; - if (!y(s)) - throw new ReferenceError( - `You are trying to end an ended transition: ${t}` - ); - this.ticker.remove(this.transitionFn[t]), - delete this.transitionFn[t], - (this.applying[t] = !1), - this.hook("end"), - e || (this.now[t] = this.target[t]); - } - } - const T = (...n) => n.reduce((i, t) => i + t, 0), - b = (n) => { - if (n === 0) return 1; - let i = n; - for (; n > 1; ) n--, (i *= n); - return i; - }, - A = (n, i) => Math.round(b(i) / (b(n) * b(i - n))), - p = (n, i, t = (e) => 1 - i(1 - e)) => - n === "in" - ? i - : n === "out" - ? t - : n === "in-out" - ? (e) => (e < 0.5 ? i(e * 2) / 2 : 0.5 + t((e - 0.5) * 2) / 2) - : (e) => (e < 0.5 ? t(e * 2) / 2 : 0.5 + i((e - 0.5) * 2) / 2), - $ = Math.cosh(2), - z = Math.acosh(2), - V = Math.tanh(3), - P = Math.atan(5); - - function Y() { - return (n) => n; - } - - function q(...n) { - const i = [0].concat(n); - i.push(1); - const t = i.length, - e = Array(t) - .fill(0) - .map((s, r) => A(r, t - 1)); - return (s) => { - const r = e.map((l, a) => l * i[a] * (1 - s) ** (t - a - 1) * s ** a); - return T(...r); - }; - } - - function U(n, i) { - if (n === "sin") { - const t = (s) => Math.sin((s * Math.PI) / 2); - return p(i, (s) => 1 - t(1 - s), t); - } - if (n === "sec") { - const t = (s) => 1 / Math.cos(s); - return p(i, (s) => t((s * Math.PI) / 3) - 1); - } - throw new TypeError( - "Unexpected parameters are delivered in trigo timing function." - ); - } - - function C(n, i) { - if (!Number.isInteger(n)) - throw new TypeError( - "The first parameter of power timing function only allow integer." - ); - return p(i, (e) => e ** n); - } - - function G(n, i) { - if (n === "sin") return p(i, (e) => (Math.cosh(e * 2) - 1) / ($ - 1)); - if (n === "tan") { - const t = (s) => (Math.tanh(s * 3) * 1) / V; - return p(i, (s) => 1 - t(1 - s), t); - } - if (n === "sec") { - const t = (s) => 1 / Math.cosh(s); - return p(i, (s) => 1 - (t(s * z) - 0.5) * 2); - } - throw new TypeError( - "Unexpected parameters are delivered in hyper timing function." - ); - } - - function N(n, i) { - if (n === "sin") { - const t = (s) => (Math.asin(s) / Math.PI) * 2; - return p(i, (s) => 1 - t(1 - s), t); - } - if (n === "tan") { - const t = (s) => Math.atan(s * 5) / P; - return p(i, (s) => 1 - t(1 - s), t); - } - throw new TypeError( - "Unexpected parameters are delivered in inverse trigo timing function." - ); - } - - function B(n, i = () => 1) { - let t = -1; - return (e) => ( - (t *= -1), e < 0.5 ? n * i(e * 2) * t : n * i((1 - e) * 2) * t - ); - } - - function D(n, i = 1, t = [0, 0], e = 0, s = (l) => 1, r = !1) { - return (l) => { - const a = i * l * Math.PI * 2 + (e * Math.PI) / 180, - u = Math.cos(a), - c = Math.sin(a), - h = n * s(s(r ? 1 - l : l)); - return [h * u + t[0], h * c + t[1]]; - }; - } - - function H(n, i, ...t) { - const e = [n].concat(t); - e.push(i); - const s = e.length, - r = Array(s) - .fill(0) - .map((l, a) => A(a, s - 1)); - return (l) => { - const a = r.map( - (c, h) => c * e[h][0] * (1 - l) ** (s - h - 1) * l ** h - ), - u = r.map((c, h) => c * e[h][1] * (1 - l) ** (s - h - 1) * l ** h); - return [T(...a), T(...u)]; - }; - } - if ("animate" in core.plugin) - throw new ReferenceError(`插件中已存在名为animate的属性!`); - - core.plugin.animate = { - Animation: j, - AnimationBase: F, - Ticker: I, - Transition: O, - bezier: q, - bezierPath: H, - circle: D, - hyper: G, - linear: Y, - power: C, - shake: B, - sleep: R, - trigo: U, - inverseTrigo: N, - }; - }, + // 还记得刚开始定义的async function 吗,直接调用它就能执行动画了! + // 示例:ani(); // 执行刚刚写的所有动画 + + // 8. 自定义速率曲线和路径 + + // 该插件中,速率曲线和路径均可自定义 + + // 对于速率曲线,其类型为 (input: number) => number + // 它接受一个范围在 0-1 的值,输出一个 0-1 的值,表示了动画的完成度,1表示动画已完成,0表示动画刚开始(当前大于1小于0也不会报错,也会执行相应的动画) + + // 对于路径,其类型为 (input: number) => [number, number] + // 它与速率曲线类似,接收一个 0-1 的值,输出一个坐标数组 + + // 9. 多个属性绑定 + + // 该插件中,你可以绑定多个动画属性,你可以使用ani.bind(...attr)来绑定。 + // 绑定之后,这三个动画属性可以被一个返回了长度为3的数组的渐变函数执行。 + // 绑定使用ani.bind,设置渐变函数仍然使用ani.mode,注意它与单个动画属性是分开的,也就是它不会影响正常的渐变函数。 + // 然后使用ani.applyMulti即可执行动画 + + // 例如: + // // 自定义的一个三属性渐变函数 + // function b(input) { + // return [input * 100, input ** 2 * 100, input ** 3 * 100]; + // } + // ani.bind('a', 'b', 'c') // 这样会绑定abc这三个动画属性 + // .mode(b) // 自定义的一个返回了长度为3的数组的函数 + // .time(5000) + // .absolute() + // .applyMulti(); // 执行这个动画 + + // 9. 监听 动画的生命周期钩子 + + // 这个插件还允许你去监听动画的状态,可以监听动画的开始、结束、运行 + // 你可以使用 animate.listen(type, fn)来监听,fn的类型是 (a: Animation, type: string) => void + // 当然,一般情况下你不会用到这个功能,插件中已经帮你包装了三个等待函数,他们就是以这些监听为基础的 + + // 10. 自定义时间获取函数 + + // 你可以修改ani.getTime来修改动画的时间获取函数,例如想让动画速度变成一半可以写ani.getTime = () => Date.now() / 2 + // 这样可以允许你随意控制动画的运行速度,暂停,甚至是倒退。该值默认为`Date.now` + + // -------------------- 渐变使用教程 -------------------- // + + // 相比于动画,渐变属于一种较为简便的动画,它可以让你在设置一个属性后使属性缓慢变化值目标值而不是突变至目标值 + // 现在假设你已经了解了动画的使用,下面我们来了解渐变。 + + // 1. 创建一个渐变实例 + // 与动画类似,你需要使用new来实例化一个渐变,当然别忘了引入 + // const { Transition } = core.plugin.animate; + // const tran = new Transition(); + + // 2. 绘制 + // const ctx = core.createCanvas('transition', 0, 0, 416, 416, 100); + // ctx.save(); + // const fn = () => { + // ctx.restore(); + // ctx.save(); + // ctx.clearRect(0, 0, 800, 800); + // ctx.beginPath(); + // ctx.arc(tran.value.x, tran.value.y, 50, 0, Math.PI * 2); // 使用tran.value.xxx获取当前的属性 + // ctx.fill(); + // // 当然也可以用样板的api,例如core.fillCircle();等 + // } + // animate.ticker.add(fn); + + // 3. 设置渐变 + // 同样,与动画类似,你可以使用tran.time()设置渐变时间,使用tran.mode()设置渐变函数,使用tran.absolute()和tran.relative()设置相对模式 + // 例如: + // tran.time(1000) + // .mode(hyper('sin', 'out')) + // .absolute(); + + // 4. 初始化渐变属性 + // 与动画不同的是,动画在执行一个自定义属性前都需要register,而渐变不需要。 + // 你可以通过tran.value.xxx = yyy来设置动画属性或使用tran.transition('xxx', yyy)来设置 + // 你的首次赋值即是初始化了渐变属性,这时是不会执行渐变的,例如: + // tran.value.x = 200; + // tran.transition('y', 200); + // 上述例子便是将 x 和 y 初始化成了200 + + // 5. 执行渐变 + // 初始化完成后,便可以直接执行渐变了,有两种方法 + // tran.value.x = 400; // 将 x 缓慢移动至400 + // tran.transition('y', 400); // 将 y 缓慢移动至400 + + // 6. 自定义时间获取函数 + // 与动画类似,你依然可以通过修改tran.getTime来修改时间获取函数 + + if (main.replayChecking) return (core.plugin.animate = {}); + + var M = Object.defineProperty; + var E = (n, i, t) => + i in n ? + M(n, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : + (n[i] = t); + var o = (n, i, t) => (E(n, typeof i != "symbol" ? i + "" : i, t), t); + let w = []; + const k = (n) => { + for (const i of w) + if (i.status === "running") + try { + for (const t of i.funcs) t(n - i.startTime); + } catch (t) { + i.destroy(), console.error(t); + } + requestAnimationFrame(k); + }; + requestAnimationFrame(k); + class I { + constructor() { + o(this, "funcs", /* @__PURE__ */ new Set()); + o(this, "status", "stop"); + o(this, "startTime", 0); + (this.status = "running"), + w.push(this), + requestAnimationFrame((i) => (this.startTime = i)); + } + add(i) { + return this.funcs.add(i), this; + } + remove(i) { + return this.funcs.delete(i), this; + } + clear() { + this.funcs.clear(); + } + destroy() { + this.clear(), this.stop(); + } + stop() { + (this.status = "stop"), (w = w.filter((i) => i !== this)); + } + } + class F { + constructor() { + o(this, "timing"); + o(this, "relation", "absolute"); + o(this, "easeTime", 0); + o(this, "applying", {}); + o(this, "getTime", Date.now); + o(this, "ticker", new I()); + o(this, "value", {}); + o(this, "listener", {}); + this.timing = (i) => i; + } + async all() { + if (Object.values(this.applying).every((i) => i === !0)) + throw new ReferenceError("There is no animates to be waited."); + await new Promise((i) => { + const t = () => { + Object.values(this.applying).every((e) => e === !1) && + (this.unlisten("end", t), i("all animated.")); + }; + this.listen("end", t); + }); + } + async n(i) { + const t = Object.values(this.applying).filter((s) => s === !0).length; + if (t < i) + throw new ReferenceError( + `You are trying to wait ${i} animate, but there are only ${t} animate animating.` + ); + let e = 0; + await new Promise((s) => { + const r = () => { + e++, e === i && (this.unlisten("end", r), s(`${i} animated.`)); + }; + this.listen("end", r); + }); + } + async w(i) { + if (this.applying[i] === !1) + throw new ReferenceError(`The ${i} animate is not animating.`); + await new Promise((t) => { + const e = () => { + this.applying[i] === !1 && + (this.unlisten("end", e), t(`${i} animated.`)); + }; + this.listen("end", e); + }); + } + listen(i, t) { + var e, s; + (s = (e = this.listener)[i]) != null || (e[i] = []), + this.listener[i].push(t); + } + unlisten(i, t) { + const e = this.listener[i].findIndex((s) => s === t); + if (e === -1) + throw new ReferenceError( + "You are trying to remove a nonexistent listener." + ); + this.listener[i].splice(e, 1); + } + hook(...i) { + const t = Object.entries(this.listener).filter((e) => i.includes(e[0])); + for (const [e, s] of t) + for (const r of s) r(this, e); + } + } + + function y(n) { + return n != null; + } + async function R(n) { + return new Promise((i) => setTimeout(i, n)); + } + class j extends F { + constructor() { + super(); + o(this, "shakeTiming"); + o(this, "path"); + o(this, "multiTiming"); + o(this, "value", {}); + o(this, "size", 1); + o(this, "angle", 0); + o(this, "targetValue", { + system: { + move: [0, 0], + moveAs: [0, 0], + resize: 0, + rotate: 0, + shake: 0, + "@@bind": [], + }, + custom: {}, + }); + o(this, "animateFn", { + system: { + move: [() => 0, () => 0], + moveAs: () => 0, + resize: () => 0, + rotate: () => 0, + shake: () => 0, + "@@bind": () => 0, + }, + custom: {}, + }); + o(this, "ox", 0); + o(this, "oy", 0); + o(this, "sx", 0); + o(this, "sy", 0); + o(this, "bindInfo", []); + (this.timing = (t) => t), + (this.shakeTiming = (t) => t), + (this.multiTiming = (t) => [t, t]), + (this.path = (t) => [t, t]), + (this.applying = { + move: !1, + scale: !1, + rotate: !1, + shake: !1, + }), + this.ticker.add(() => { + const { running: t } = this.listener; + if (y(t)) + for (const e of t) e(this, "running"); + }); + } + get x() { + return this.ox + this.sx; + } + get y() { + return this.oy + this.sy; + } + mode(t, e = !1) { + return ( + typeof t(0) == "number" ? + e ? + (this.shakeTiming = t) : + (this.timing = t) : + (this.multiTiming = t), + this + ); + } + time(t) { + return (this.easeTime = t), this; + } + relative() { + return (this.relation = "relative"), this; + } + absolute() { + return (this.relation = "absolute"), this; + } + bind(...t) { + return ( + this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), + (this.bindInfo = t), + this + ); + } + unbind() { + return ( + this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), + (this.bindInfo = []), + this + ); + } + move(t, e) { + return ( + this.applying.move && this.end(!0, "move"), + this.applySys("ox", t, "move"), + this.applySys("oy", e, "move"), + this + ); + } + rotate(t) { + return this.applySys("angle", t, "rotate"), this; + } + scale(t) { + return this.applySys("size", t, "resize"), this; + } + shake(t, e) { + this.applying.shake === !0 && this.end(!0, "shake"), + (this.applying.shake = !0); + const { easeTime: s, shakeTiming: r } = this, + l = this.getTime(); + if ((this.hook("start", "shakestart"), s <= 0)) + return this.end(!1, "shake"), this; + const a = () => { + const c = this.getTime() - l; + if (c > s) { + this.ticker.remove(a), + (this.applying.shake = !1), + (this.sx = 0), + (this.sy = 0), + this.hook("end", "shakeend"); + return; + } + const h = c / s, + m = r(h); + (this.sx = m * t), (this.sy = m * e); + }; + return this.ticker.add(a), (this.animateFn.system.shake = a), this; + } + moveAs(t) { + this.applying.moveAs && this.end(!0, "moveAs"), + (this.applying.moveAs = !0), + (this.path = t); + const { easeTime: e, relation: s, timing: r } = this, + l = this.getTime(), + [a, u] = [this.x, this.y], + [c, h] = (() => { + if (s === "absolute") return t(1); { + const [d, f] = t(1); + return [a + d, u + f]; + } + })(); + if ((this.hook("start", "movestart"), e <= 0)) + return this.end(!1, "moveAs"), this; + const m = () => { + const f = this.getTime() - l; + if (f > e) { + this.end(!0, "moveAs"); + return; + } + const g = f / e, + [v, x] = t(r(g)); + s === "absolute" ? + ((this.ox = v), (this.oy = x)) : + ((this.ox = a + v), (this.oy = u + x)); + }; + return ( + this.ticker.add(m), + (this.animateFn.system.moveAs = m), + (this.targetValue.system.moveAs = [c, h]), + this + ); + } + register(t, e) { + if (typeof this.value[t] == "number") + return this.error( + `Property ${t} has been regietered twice.`, + "reregister" + ); + (this.value[t] = e), (this.applying[t] = !1); + } + apply(t, e) { + this.applying[t] === !0 && this.end(!1, t), + t in this.value || + this.error(`You are trying to execute nonexistent property ${t}.`), + (this.applying[t] = !0); + const s = this.value[t], + r = this.getTime(), + { timing: l, relation: a, easeTime: u } = this, + c = a === "absolute" ? e - s : e; + if ((this.hook("start"), u <= 0)) return this.end(!1, t), this; + const h = () => { + const d = this.getTime() - r; + if (d > u) { + this.end(!1, t); + return; + } + const f = d / u, + g = l(f); + this.value[t] = s + g * c; + }; + return ( + this.ticker.add(h), + (this.animateFn.custom[t] = h), + (this.targetValue.custom[t] = c + s), + this + ); + } + applyMulti() { + this.applying["@@bind"] === !0 && this.end(!1, "@@bind"), + (this.applying["@@bind"] = !0); + const t = this.bindInfo, + e = t.map((h) => this.value[h]), + s = this.getTime(), + { multiTiming: r, relation: l, easeTime: a } = this, + u = r(1); + if (u.length !== e.length) + throw new TypeError( + `The number of binded animate attributes and timing function returns's length does not match. binded: ${t.length}, timing: ${u.length}` + ); + if ((this.hook("start"), a <= 0)) return this.end(!1, "@@bind"), this; + const c = () => { + const m = this.getTime() - s; + if (m > a) { + this.end(!1, "@@bind"); + return; + } + const d = m / a, + f = r(d); + t.forEach((g, v) => { + l === "absolute" ? + (this.value[g] = f[v]) : + (this.value[g] = e[v] + f[v]); + }); + }; + return ( + this.ticker.add(c), + (this.animateFn.custom["@@bind"] = c), + (this.targetValue.system["@@bind"] = u), + this + ); + } + applySys(t, e, s) { + s !== "move" && this.applying[s] === !0 && this.end(!0, s), + (this.applying[s] = !0); + const r = this[t], + l = this.getTime(), + a = this.timing, + u = this.relation, + c = this.easeTime, + h = u === "absolute" ? e - r : e; + if ((this.hook("start", `${s}start`), c <= 0)) return this.end(!0, s); + const m = () => { + const f = this.getTime() - l; + if (f > c) { + this.end(!0, s); + return; + } + const g = f / c, + v = a(g); + (this[t] = r + h * v), t !== "oy" && this.hook(s); + }; + this.ticker.add(m), + t === "ox" ? + (this.animateFn.system.move[0] = m) : + t === "oy" ? + (this.animateFn.system.move[1] = m) : + (this.animateFn.system[s] = m), + s === "move" ? + (t === "ox" && (this.targetValue.system.move[0] = h + r), + t === "oy" && (this.targetValue.system.move[1] = h + r)) : + s !== "shake" && (this.targetValue.system[s] = h + r); + } + error(t, e) { + throw e === "repeat" ? + new Error(`Cannot execute the same animation twice. Info: ${t}`) : + e === "reregister" ? + new Error(`Cannot register an animated property twice. Info: ${t}`) : + new Error(t); + } + end(t, e) { + if (t === !0) + if ( + ((this.applying[e] = !1), + e === "move" ? + (this.ticker.remove(this.animateFn.system.move[0]), + this.ticker.remove(this.animateFn.system.move[1])) : + e === "moveAs" ? + this.ticker.remove(this.animateFn.system.moveAs) : + e === "@@bind" ? + this.ticker.remove(this.animateFn.system["@@bind"]) : + this.ticker.remove(this.animateFn.system[e]), + e === "move") + ) { + const [s, r] = this.targetValue.system.move; + (this.ox = s), (this.oy = r), this.hook("moveend", "end"); + } else if (e === "moveAs") { + const [s, r] = this.targetValue.system.moveAs; + (this.ox = s), (this.oy = r), this.hook("moveend", "end"); + } else + e === "rotate" ? + ((this.angle = this.targetValue.system.rotate), + this.hook("rotateend", "end")) : + e === "resize" ? + ((this.size = this.targetValue.system.resize), + this.hook("resizeend", "end")) : + e === "@@bind" ? + this.bindInfo.forEach((r, l) => { + this.value[r] = this.targetValue.system["@@bind"][l]; + }) : + ((this.sx = 0), (this.sy = 0), this.hook("shakeend", "end")); + else + (this.applying[e] = !1), + this.ticker.remove(this.animateFn.custom[e]), + (this.value[e] = this.targetValue.custom[e]), + this.hook("end"); + } + } + class O extends F { + constructor() { + super(); + o(this, "now", {}); + o(this, "target", {}); + o(this, "transitionFn", {}); + o(this, "value"); + o(this, "handleSet", (t, e, s) => (this.transition(e, s), !0)); + o(this, "handleGet", (t, e) => this.now[e]); + (this.timing = (t) => t), + (this.value = new Proxy(this.target, { + set: this.handleSet, + get: this.handleGet, + })); + } + mode(t) { + return (this.timing = t), this; + } + time(t) { + return (this.easeTime = t), this; + } + relative() { + return (this.relation = "relative"), this; + } + absolute() { + return (this.relation = "absolute"), this; + } + transition(t, e) { + if (e === this.target[t]) return this; + if (!y(this.now[t])) return (this.now[t] = e), this; + this.applying[t] && this.end(t, !0), + (this.applying[t] = !0), + this.hook("start"); + const s = this.getTime(), + r = this.easeTime, + l = this.timing, + a = this.now[t], + u = e + (this.relation === "absolute" ? 0 : a), + c = u - a; + this.target[t] = u; + const h = () => { + const d = this.getTime() - s; + if (d >= r) { + this.end(t); + return; + } + const f = d / r; + (this.now[t] = l(f) * c + a), this.hook("running"); + }; + return ( + (this.transitionFn[t] = h), + this.ticker.add(h), + r <= 0 ? (this.end(t), this) : this + ); + } + end(t, e = !1) { + const s = this.transitionFn[t]; + if (!y(s)) + throw new ReferenceError( + `You are trying to end an ended transition: ${t}` + ); + this.ticker.remove(this.transitionFn[t]), + delete this.transitionFn[t], + (this.applying[t] = !1), + this.hook("end"), + e || (this.now[t] = this.target[t]); + } + } + const T = (...n) => n.reduce((i, t) => i + t, 0), + b = (n) => { + if (n === 0) return 1; + let i = n; + for (; n > 1;) n--, (i *= n); + return i; + }, + A = (n, i) => Math.round(b(i) / (b(n) * b(i - n))), + p = (n, i, t = (e) => 1 - i(1 - e)) => + n === "in" ? + i : + n === "out" ? + t : + n === "in-out" ? + (e) => (e < 0.5 ? i(e * 2) / 2 : 0.5 + t((e - 0.5) * 2) / 2) : + (e) => (e < 0.5 ? t(e * 2) / 2 : 0.5 + i((e - 0.5) * 2) / 2), + $ = Math.cosh(2), + z = Math.acosh(2), + V = Math.tanh(3), + P = Math.atan(5); + + function Y() { + return (n) => n; + } + + function q(...n) { + const i = [0].concat(n); + i.push(1); + const t = i.length, + e = Array(t) + .fill(0) + .map((s, r) => A(r, t - 1)); + return (s) => { + const r = e.map((l, a) => l * i[a] * (1 - s) ** (t - a - 1) * s ** a); + return T(...r); + }; + } + + function U(n, i) { + if (n === "sin") { + const t = (s) => Math.sin((s * Math.PI) / 2); + return p(i, (s) => 1 - t(1 - s), t); + } + if (n === "sec") { + const t = (s) => 1 / Math.cos(s); + return p(i, (s) => t((s * Math.PI) / 3) - 1); + } + throw new TypeError( + "Unexpected parameters are delivered in trigo timing function." + ); + } + + function C(n, i) { + if (!Number.isInteger(n)) + throw new TypeError( + "The first parameter of power timing function only allow integer." + ); + return p(i, (e) => e ** n); + } + + function G(n, i) { + if (n === "sin") return p(i, (e) => (Math.cosh(e * 2) - 1) / ($ - 1)); + if (n === "tan") { + const t = (s) => (Math.tanh(s * 3) * 1) / V; + return p(i, (s) => 1 - t(1 - s), t); + } + if (n === "sec") { + const t = (s) => 1 / Math.cosh(s); + return p(i, (s) => 1 - (t(s * z) - 0.5) * 2); + } + throw new TypeError( + "Unexpected parameters are delivered in hyper timing function." + ); + } + + function N(n, i) { + if (n === "sin") { + const t = (s) => (Math.asin(s) / Math.PI) * 2; + return p(i, (s) => 1 - t(1 - s), t); + } + if (n === "tan") { + const t = (s) => Math.atan(s * 5) / P; + return p(i, (s) => 1 - t(1 - s), t); + } + throw new TypeError( + "Unexpected parameters are delivered in inverse trigo timing function." + ); + } + + function B(n, i = () => 1) { + let t = -1; + return (e) => ( + (t *= -1), e < 0.5 ? n * i(e * 2) * t : n * i((1 - e) * 2) * t + ); + } + + function D(n, i = 1, t = [0, 0], e = 0, s = (l) => 1, r = !1) { + return (l) => { + const a = i * l * Math.PI * 2 + (e * Math.PI) / 180, + u = Math.cos(a), + c = Math.sin(a), + h = n * s(s(r ? 1 - l : l)); + return [h * u + t[0], h * c + t[1]]; + }; + } + + function H(n, i, ...t) { + const e = [n].concat(t); + e.push(i); + const s = e.length, + r = Array(s) + .fill(0) + .map((l, a) => A(a, s - 1)); + return (l) => { + const a = r.map( + (c, h) => c * e[h][0] * (1 - l) ** (s - h - 1) * l ** h + ), + u = r.map((c, h) => c * e[h][1] * (1 - l) ** (s - h - 1) * l ** h); + return [T(...a), T(...u)]; + }; + } + if ("animate" in core.plugin) + throw new ReferenceError(`插件中已存在名为animate的属性!`); + + core.plugin.animate = { + Animation: j, + AnimationBase: F, + Ticker: I, + Transition: O, + bezier: q, + bezierPath: H, + circle: D, + hyper: G, + linear: Y, + power: C, + shake: B, + sleep: R, + trigo: U, + inverseTrigo: N, + }; +}, "func": function () { // 功能函数集,具体有哪些函数看每个函数前的注释即可 // 安装方式:直接复制到插件里面,注意新建插件自带的 function () { } 不能删 @@ -22084,29 +22085,15 @@ let time=0 x = px; y = py; } - bookclick(x, y) + core.book.bookclick(x, y) } catch (ee) { main.log(ee); } } - function bookclick(x, y) { //点击画布(x,y)触发的效果 - const makeBox = ([x, y], [w, h]) => { //创建点击检测区域 - return [ - [x, y], - [x + w, y + h], - ]; - }; - const pos = [x, y]; //创建点击坐标pos - const inRect = ([x, y], [ //检测点击坐标是否在检测区域中 - [sx, sy], - [dx, dy] - ]) => { - return sx <= x && x <= dx && sy <= y && y <= dy; - }; - } + const { Animation, linear, sleep, trigo } = core.plugin.animate class Book { constructor() { this.width = 300 @@ -22114,9 +22101,1322 @@ let time=0 this.pagemax = 1 this.page = 0 this.paperpages = [] - - + this.ani = new Animation() + this.time = 1000 } + bookclick(x, y) { //点击画布(x,y)触发的效果 + const makeBox = ([x, y], [w, h]) => { //创建点击检测区域 + return [ + [x, y], + [x + w, y + h], + ]; + }; + const pos = [x, y]; //创建点击坐标pos + const inRect = ([x, y], [ //检测点击坐标是否在检测区域中 + [sx, sy], + [dx, dy] + ]) => { + return sx <= x && x <= dx && sy <= y && y <= dy; + }; + const leftbox = makeBox([(676 - this.width * 2) / 2, (416 - this.height) / 2], [this.width, this.height]) + const rightbox = makeBox([338, (416 - this.height) / 2], [this.width, this.height]) + if (this.isAnimate) return + if (inRect(pos, leftbox)) { + + this.turnleft() + } else if (inRect(pos, rightbox)) { + + this.turnright() + } + } + async turnleft() { + if (this.page <= 1) return + this.isAnimate = true + await this.ani.time(1) + .absolute() + .mode(linear()).move(0, 0).n(1) + + const fn = () => { + if (core.domStyle.isVertical) { + core.maps._setHDCanvasSize(ctx, 416, 676) + ctx.save(); + ctx.translate(416, 0); + ctx.rotate(Math.PI / 2); + } else { + core.maps._setHDCanvasSize(ctx, 676, 416) + ctx.save(); + } + + // 1. 先绘制背景底色 + core.fillRect(ctx, 0, 0, 676, 416, "#000000") + + // 2. 绘制书脊和封面背景 + const dx = (676 - this.width * 2) / 2, + dy = (416 - this.height) / 2 + + + //绘制打开的书皮,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); + + + //绘制上一页左页 + core.drawImage(ctx, this.paperpages[this.page - 2][0], 0, 0, this.ani.x / (this.width * 2) * 300, 400, dx, dy, this.ani.x / 2, this.height); + //绘制翻起的右页 + core.drawImage(ctx, this.paperpages[this.page - 2][1], 300 - this.ani.x / (this.width * 2) * 300, 0, this.ani.x / (this.width * 2) * 300, 400, this.ani.x / 2 + dx, dy, this.ani.x / 2, this.height); + const gradientleft = ctx.createLinearGradient(dx + this.ani.x / 2 - 20, (416 - this.height) / 2, dx + this.ani.x / 2 + 10, (416 - this.height) / 2) + gradientleft.addColorStop(0, "rgba(0,0,0,0)") + gradientleft.addColorStop(0.67, "rgba(0,0,0,0.2)") + gradientleft.addColorStop(1, "rgba(0,0,0,0)") + core.fillRect(ctx, dx + this.ani.x / 2 - 20, (416 - this.height) / 2, 30, this.height, gradientleft) + const gradientright = ctx.createLinearGradient(this.ani.x + dx, (416 - this.height) / 2, this.ani.x + dx + 20, (416 - this.height) / 2) + gradientright.addColorStop(1, "rgba(0,0,0,0)") + gradientright.addColorStop(0.2, "rgba(0,0,0,0.2)") + gradientright.addColorStop(0, "rgba(0,0,0,0.4)") + core.fillRect(ctx, this.ani.x + dx, (416 - this.height) / 2, 20, this.height, gradientright) + + // 重置阴影 + ctx.shadowColor = "transparent"; + + + ctx.restore(); + } + this.ani.ticker.add(fn) + await this.ani.time(this.time) + .absolute() + .mode(trigo("sin", "in-out")) + .move(this.width * 2, 0).n(1) + this.ani.ticker.clear() + this.isAnimate = false + this.page-- + this.update() + } + async turnright() { + if (this.page >= this.pagemax) return + this.isAnimate = true + await this.ani.time(1) + .absolute() + .mode(linear()).move(0, 0).n(1) + + const fn = () => { + if (core.domStyle.isVertical) { + core.maps._setHDCanvasSize(ctx, 416, 676) + ctx.save(); + ctx.translate(416, 0); + ctx.rotate(Math.PI / 2); + } else { + core.maps._setHDCanvasSize(ctx, 676, 416) + ctx.save(); + } + + // 1. 先绘制背景底色 + core.fillRect(ctx, 0, 0, 676, 416, "#000000") + + // 2. 绘制书脊和封面背景 + const dx = (676 - this.width * 2) / 2, + dy = (416 - this.height) / 2 + + + //绘制打开的书皮,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); + //绘制翻起的左页 + core.drawImage(ctx, this.paperpages[this.page][0], 0, 0, this.ani.x / (this.width * 2) * 300, 400, 676 - dx - this.ani.x, dy, this.ani.x / 2, this.height); + //绘制下一页右页 + core.drawImage(ctx, this.paperpages[this.page][1], 300 - this.ani.x / (this.width * 2) * 300, 0, this.ani.x / (this.width * 2) * 300, 400, 676 - dx - this.ani.x / 2, dy, this.ani.x / 2, this.height); + const gradientleft = ctx.createLinearGradient(676 - dx - this.ani.x, (416 - this.height) / 2, 676 - dx - this.ani.x - 20, (416 - this.height) / 2) + gradientleft.addColorStop(1, "rgba(0,0,0,0)") + gradientleft.addColorStop(0.2, "rgba(0,0,0,0.2)") + gradientleft.addColorStop(0, "rgba(0,0,0,0.4)") + core.fillRect(ctx, 676 - dx - this.ani.x - 20, (416 - this.height) / 2, 20, this.height, gradientleft) + const gradientright = ctx.createLinearGradient(676 - dx - this.ani.x / 2 - 10, (416 - this.height) / 2, 676 - dx - this.ani.x / 2 + 20, (416 - this.height) / 2) + gradientright.addColorStop(1, "rgba(0,0,0,0)") + gradientright.addColorStop(0.33, "rgba(0,0,0,0.2)") + gradientright.addColorStop(0, "rgba(0,0,0,0)") + core.fillRect(ctx, 676 - dx - this.ani.x / 2 - 10, (416 - this.height) / 2, 30, this.height, gradientright) + // 重置阴影 + ctx.shadowColor = "transparent"; + + + ctx.restore(); + } + this.ani.ticker.add(fn) + await this.ani.time(this.time) + .absolute() + .mode(trigo("sin", "in-out")) + .move(this.width * 2, 0).n(1) + this.ani.ticker.clear() + this.isAnimate = false + this.page++ + this.update() + } + paperTexture(dir, num = 0) { const textureCanvas = document.createElement('canvas'); textureCanvas.width = 300; @@ -22136,7 +23436,7 @@ let time=0 ctx.beginPath(); // 生成更自然的毛边函数 - const createTornEdge = (startX, startY, endX, endY, intensity, steps, top = false) => { + const createTornEdge = (startX, startY, endX, endY, intensity, steps) => { const points = []; const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); const baseStep = length / steps; @@ -22148,11 +23448,11 @@ let time=0 const x = startX + (endX - startX) * t; const y = startY + (endY - startY) * t // 使用噪声函数生成更自然的毛边 - const noise = Math.sin(t * Math.PI * 4) * intensity * 0.5 + + const noise = Math.sin(t * Math.PI * 4 * Math.random()) * intensity * 0.5 + Math.sin(t * Math.PI * 8) * intensity * 0.3 + - Math.sin(t * Math.PI * 16) * intensity * 0.2; + Math.sin(t * Math.PI * 16 * Math.random()) * intensity * 0.2; - const perpendicularX = -(endY - startY) / length; + const perpendicularX = (endY - startY) / length; const perpendicularY = (endX - startX) / length; if (t < 0.2 || t > 0.8) points.push({ x: x, @@ -22161,7 +23461,7 @@ let time=0 else points.push({ x: x + perpendicularX * noise * (0.8 + Math.random() * 0.4), - y: y + top * perpendicularY * noise * (0.8 + Math.random() * 0.4) + y: y + perpendicularY * noise * (0.8 + Math.random() * 0.4) }); } @@ -22182,7 +23482,7 @@ let time=0 // 上边缘曲线(添加毛边) - createTornEdge(0, offsetY, 300, offsetY, 16, 24, true); + createTornEdge(0, offsetY, 300, offsetY, 16, 24); // 右侧边缘(根据方向决定是否添加毛边) @@ -22194,7 +23494,7 @@ let time=0 // 下边缘曲线(添加毛边) - createTornEdge(300, 400 + offsetY, 0, 400 + offsetY, 16, 24, true); + createTornEdge(300, 400 + offsetY, 0, 400 + offsetY, 16, 24); // 左侧边缘(根据方向决定是否添加毛边) @@ -22208,13 +23508,13 @@ let time=0 }; // 创建上蒙版(顶部有毛边) - createTornEdgePath(curveCtx, 0, true); + createTornEdgePath(curveCtx, 0); curveCtx.fillStyle = 'white'; curveCtx.fill(); // 创建下蒙版(底部有毛边) curveCtx.clearRect(0, 0, 300, 400); - createTornEdgePath(curveCtx, 0, false); + createTornEdgePath(curveCtx, 0); curveCtx.fillStyle = 'white'; curveCtx.fill(); @@ -22370,95 +23670,68 @@ let time=0 const shadowSaturation = baseSaturation + 15; const shadowLightness = baseLightness * 0.55; const shadowSize = 10; - + // 转换为RGB + const rgb = this.HSLtoRGB(shadowHue, shadowSaturation, shadowLightness); const shadowCanvas = document.createElement('canvas'); shadowCanvas.width = 300; shadowCanvas.height = 400; const shadowCtx = shadowCanvas.getContext('2d'); + const topShadow = ctx.createLinearGradient(0, 0, 0, shadowSize); + topShadow.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)`); + topShadow.addColorStop(1, "transparent"); + shadowCtx.fillStyle = topShadow; + shadowCtx.beginPath() + shadowCtx.moveTo(0, 0) + shadowCtx.lineTo(300, 0) + shadowCtx.lineTo(300 - shadowSize * (dir === "left" ? 0.6 : 1), shadowSize) + shadowCtx.lineTo(shadowSize * (dir === "left" ? 1 : 0.6), shadowSize) + shadowCtx.lineTo(0, 0) + shadowCtx.closePath() + shadowCtx.fill() - // 计算每个像素的阴影强度 - const gradientData = shadowCtx.createImageData(300, 400); - for (let i = 0; i < gradientData.data.length; i += 4) { - const x = (i / 4) % 300; - const y = Math.floor((i / 4) / 300); - // 基础距离计算 - const leftDist = x / shadowSize * (dir === "left" ? 1 : 0.7); - const rightDist = (300 - x) / shadowSize * (dir === "left" ? 0.7 : 1); - const topDist = y / shadowSize; - const bottomDist = (400 - y) / shadowSize; + const leftShadow = ctx.createLinearGradient(0, 0, shadowSize * (dir === "left" ? 1 : 0.6), 0); + leftShadow.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b},${dir === "left"?0.7:0.6*0.7})`); + leftShadow.addColorStop(1, "transparent"); + shadowCtx.fillStyle = leftShadow; + shadowCtx.beginPath() + shadowCtx.moveTo(0, 0) + shadowCtx.lineTo(shadowSize * (dir === "left" ? 1 : 0.6), shadowSize) + shadowCtx.lineTo(shadowSize * (dir === "left" ? 1 : 0.6), 400 - shadowSize) + shadowCtx.lineTo(0, 400) + shadowCtx.lineTo(0, 0) + shadowCtx.closePath() + shadowCtx.fill() - // 根据方向调整左右阴影强度 - let leftShadowIntensity = 0.7; - let rightShadowIntensity = 0.7; - if (dir === "left") { - rightShadowIntensity *= 0.6; // 削弱右侧阴影 - } else if (dir === "right") { - leftShadowIntensity *= 0.6; // 削弱左侧阴影 - } + const rightShadow = ctx.createLinearGradient(300, 0, 300 - shadowSize * (dir === "left" ? 0.6 : 1), 0); + rightShadow.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${dir === "left"?0.7*0.6:0.7})`); + rightShadow.addColorStop(1, "transparent"); + shadowCtx.fillStyle = rightShadow; + shadowCtx.beginPath() + shadowCtx.moveTo(300, 0) + shadowCtx.lineTo(300 - shadowSize * (dir === "left" ? 0.6 : 1), shadowSize) + shadowCtx.lineTo(300 - shadowSize * (dir === "left" ? 0.6 : 1), 400 - shadowSize) + shadowCtx.lineTo(300, 400) + shadowCtx.lineTo(3000, 0) + shadowCtx.closePath() + shadowCtx.fill() - // 计算角部距离 - const tlDist = Math.sqrt(x * 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((300 - x) * (300 - x) + (400 - y) * (400 - y)) / (shadowSize * 1.4); + const bottomShadow = ctx.createLinearGradient(0, 400, 0, 400 - shadowSize); + bottomShadow.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)`); + bottomShadow.addColorStop(1, "transparent"); + shadowCtx.fillStyle = bottomShadow; + shadowCtx.beginPath() + shadowCtx.moveTo(0, 400) + shadowCtx.lineTo(shadowSize * (dir === "left" ? 1 : 0.6), 400 - shadowSize) + shadowCtx.lineTo(300 - shadowSize * (dir === "left" ? 0.6 : 1), 400 - shadowSize) + shadowCtx.lineTo(300, 400) + shadowCtx.lineTo(0, 400) + shadowCtx.closePath() + shadowCtx.fill() + shadowCtx.fillRect(0, 400 - shadowSize, 300, shadowSize); - // 综合阴影强度 - let shadowAlpha = 0; - - // 左侧阴影计算 - if (leftDist < 1) { - shadowAlpha = Math.max(shadowAlpha, leftShadowIntensity * (1 - leftDist)); - } - - // 右侧阴影计算 - if (rightDist < 1) { - shadowAlpha = Math.max(shadowAlpha, rightShadowIntensity * (1 - rightDist)); - } - - // 顶部和底部阴影(根据弯曲路径调整) - if (topDist < 1) { - - shadowAlpha = Math.max(shadowAlpha, (1 - topDist) * 0.7); - } - if (bottomDist < 1) { - - shadowAlpha = Math.max(shadowAlpha, (1 - bottomDist) * 0.7); - } - - // 角部阴影(取相邻两边强度的中间值) - if (tlDist < 1) { - const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - tlDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (trDist < 1) { - const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - trDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (blDist < 1) { - const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - blDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (brDist < 1) { - const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - brDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - - // 限制最大透明度 - shadowAlpha = Math.min(0.6, shadowAlpha); - - // 转换为RGB - const rgb = this.HSLtoRGB(shadowHue, shadowSaturation, shadowLightness); - - gradientData.data[i] = rgb.r; - gradientData.data[i + 1] = rgb.g; - gradientData.data[i + 2] = rgb.b; - gradientData.data[i + 3] = shadowAlpha * 255; - } - - shadowCtx.putImageData(gradientData, 0, 0); const createHighlight = () => { const highlightCanvas = document.createElement('canvas'); @@ -22469,26 +23742,26 @@ let time=0 // 根据方向设置高光位置 const gradient = dir === 'right' ? - highlightCtx.createLinearGradient(0, 0, 20, 0) : - highlightCtx.createLinearGradient(280, 0, 300, 0); + highlightCtx.createLinearGradient(0, 0, 10, 0) : + highlightCtx.createLinearGradient(300, 0, 290, 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)'); + gradient.addColorStop(1, 'rgba(0,0,0,0)'); + gradient.addColorStop(0.2, 'rgba(0,0,0,0.4)'); + gradient.addColorStop(0, 'rgba(0,0,0,0.7)'); highlightCtx.fillStyle = gradient; - highlightCtx.fillRect(dir === 'right' ? 0 : 280, 0, 20, 400); + highlightCtx.fillRect(dir === 'right' ? 0 : 290, 0, 10, 400); return highlightCanvas; }; - + //应用高光 + contentCtx.drawImage(createHighlight(), 0, 0) // 应用阴影 contentCtx.globalCompositeOperation = 'multiply'; contentCtx.drawImage(shadowCanvas, 0, 0); contentCtx.globalCompositeOperation = 'source-over' - //应用高光 - contentCtx.drawImage(createHighlight(), 0, 0) + } addMasterfulEdgeShadows(dir); @@ -23451,17 +24724,15 @@ let time=0 a.push([this.paperTexture('left'), this.paperTexture('right')]) } this.paperpages = a + this.pagemax = num } 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]); - } + + v[0].remove(); + + v[1].remove(); + }) this.paperpages = []