Canvas 实现元球融合效果🔮

写在最前

近期使用Canvas实现一款关于"元球融合"的效果,本想是用 threejs 蹭一波跨窗口球体融合特效的热度,但发现已经为时已晚了。索性就用Canvas实现元球融合效果吧,冲!

原理

原理非常非常简单,只需求出这4个点的位置然后画出三阶贝塞尔曲线就行。

步骤

  1. 初始化画布
  2. 画圆
  3. 获取圆上点P1坐标
  4. 获取圆过点P1切线上的一点P3
  5. 画贝塞尔曲线

初始化画布

js 复制代码
let c1 = { x: 300, y: 430, radius: 80 };
let c2 = { x: 0, y: 0, radius: 80 };

let canvas = document.querySelector("#canvas") as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context = canvas.getContext("2d")!;

setCanvasBg();
js 复制代码
/**
 * 设置背景色
 */

function setCanvasBg() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, canvas.width, canvas.height);
}

画圆

封装画圆函数

js 复制代码
/**
 * 画圆
 * @param x
 * @param y
 * @param radius
 */

function drawCircle(x, y, radius) {
    context.save();
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI);
    context.closePath();
    context.stroke();
    context.fillStyle = "#FFFFFF";
    context.fill();
    context.restore();
}

我们写一个draw函数用于绘制

js 复制代码
function draw(){
   // 画圆
    drawCircle(c1.x, c1.y, c1.radius);
    drawCircle(c2.x, c2.y, c2.radius);
   
   // 画贝塞尔曲线 
}

获取圆上坐标

想要得到较为丝滑的效果,圆上点坐标与切线点坐标是需要可变化的。

我们可以看到当小球在同一水平上,两小球距离越远,在圆上的点是会稍微往两球中间挪动一段距离。

所以我们需要根据距离去设置一个最大高度的比例变量

点会在Max和MIN的方向上移动

并且两个圆上的点会根据两球位置进行一个角度的旋转,这个角度就是两球圆心的角度

所以我们将两球圆心的坐标相减,使用 Math.atan2 API可以求出角度

所以我们很容易得到以下获取圆上坐标函数

let radian = Math.atan2(c2.y - c1.y, c2.x - c1.x);

js 复制代码
/**
 * 获取圆上坐标
 * @param {*} maxHeightPercent
 */

function getCirclePoint(circle, maxHeightPercent, isReverse, radian) {
    let { x: cx, y: cy, radius } = circle;

    let thetaInRadians = Math.asin(maxHeightPercent);
    let x = Math.cos(thetaInRadians + radian) * radius;
    let y = Math.sin(thetaInRadians + radian) * radius;
    return {
        x: cx + (isReverse ? -x : x),
        y: cy + y,
    };
}

这样线上点就求出来了

获取圆过点切线上的坐标

设定切线上坐标点为T,只需要求出 线段PD与线段DT的长度。

PT 斜边 是常量我们设置的,且 ∠PTD = ∠TVC ,∠TVC = 180 - PCV - CPV; 已知切线VP 与 CP夹角为90度;则∠PVC = 90 - ∠PCV; PCV依然可以用 Math.atan2 将P,C两点带入则可以求出。

所以我们可以很轻松的得出下面函数

js 复制代码
/**
 * 获取切线上的一点
 */

function getTangentPoi(center, point, isReverse) {
    let distance = getCircleDistance();
    let length = (isReverse ? -distance : distance) / 4;

    const r = Math.atan2(point.y - center.y, point.x - center.x);
    let angle = 90 + (r / Math.PI) * 180;
    const radian = (angle / 180) * Math.PI;

    let x = Math.cos(radian) * length;
    let y = Math.sin(radian) * length;

    return {
        x: point.x + x,
        y: point.y + y,
    };
}

中间的4个点就是切线上的点

画贝塞尔曲线

canvas有贝塞尔曲线API,直接用就好了

js 复制代码
/**
 * canvas画贝塞尔曲线
 */

function drawBezierPath(maxHeightPercent) {
    let radian = Math.atan2(c2.y - c1.y, c2.x - c1.x);

    let p1Top = getCirclePoint(c1, -maxHeightPercent, false, radian);
    let p1Bottom = getCirclePoint(c1, maxHeightPercent, false, radian);
    let p2Top = getCirclePoint(c2, -maxHeightPercent, true, -radian);
    let p2Bottom = getCirclePoint(c2, maxHeightPercent, true, -radian);


    let c1tangentTop = getTangentPoi(getCircleCenterPoi(c1), p1Top, false);
    let c1tangentBottom = getTangentPoi(getCircleCenterPoi(c1), p1Bottom, true);
    let c2tangentTop = getTangentPoi(getCircleCenterPoi(c2), p2Top, true);
    let c2tangentBottom = getTangentPoi(getCircleCenterPoi(c2), p2Bottom, false);


    context.beginPath();
    context.moveTo(p1Top.x, p1Top.y);
    context.bezierCurveTo(
        c1tangentTop.x,
        c1tangentTop.y,
        c2tangentTop.x,
        c2tangentTop.y,
        p2Top.x,
        p2Top.y
    );
    context.lineTo(p2Bottom.x, p2Bottom.y);
    context.bezierCurveTo(
        c2tangentBottom.x,
        c2tangentBottom.y,
        c1tangentBottom.x,
        c1tangentBottom.y,
        p1Bottom.x,
        p1Bottom.y
    );
    context.strokeStyle = "1";
    context.lineTo(p1Top.x, p1Top.y);
    context.closePath();

    context.stroke();
    context.fillStyle = "#FFFFFF";
    context.fill();
}

至此 元球融合就算完成了

结尾

喜欢的话帮忙点个赞 + 关注吧,将持续更新 Threejs 相关的文章,谢谢浏览!

源码地址

往期链接:

🔗# Threejs 实现虚拟摇杆遨游星空 ✨✨

🔗# Threejs 让人眼前一亮的隧道穿越 🌌🌌🌌

🔗# 实现抛物线跳跃交互底部导航栏🎈🎈🎈

🔗# Threejs 中秋佳节感受闽南名俗 | 中秋博饼🥮🥮🥮

🔗# Threejs glTF编辑器功能详解🎯🎯🎯

🔗# Threejs 实现梦幻频谱可视化🧿🧿🧿

相关推荐
好开心33几秒前
js高级06-ajax封装和跨域
开发语言·前端·javascript·ajax·okhttp·ecmascript·交互
小镇程序员5 分钟前
vue2 src_Todolist消息订阅版本
前端·javascript·vue.js
Zack No Bug13 分钟前
解决报错:rror: error:0308010C:digital envelope routines::unsupported
前端·javascript·vue.js
凌虚1 小时前
Web 端语音对话 AI 示例:使用 Whisper 和 llama.cpp 构建语音聊天机器人
前端·人工智能·后端
小宇python1 小时前
Web应用安全入门:架构搭建、漏洞分析与HTTP数据包处理
前端·安全·架构
珹洺2 小时前
从 HTML 到 CSS:开启网页样式之旅(二)—— 深入探索 CSS 选择器的奥秘
前端·javascript·css·网络·html
liro2 小时前
CSS盒子模型
前端
热爱前端的小张2 小时前
包管理器
前端
冰冻果冻2 小时前
vue--制作随意滑动的相册
前端·javascript·vue.js
GISer_Jing2 小时前
前端测试工具(Jest与Mock)
前端·测试工具