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 实现梦幻频谱可视化🧿🧿🧿

相关推荐
广州华水科技1 小时前
单北斗变形监测一体机在基础设施安全与地质灾害监测中的应用价值分析
前端
Dragon Wu1 小时前
Electron Forge集成React Typescript完整步骤
前端·javascript·react.js·typescript·electron·reactjs
芳草萋萋鹦鹉洲哦1 小时前
【Tailwind】动画解读:Tailwind CSS Animation Examples
前端·css
华仔啊1 小时前
jQuery 4.0 发布,IE 终于被放弃了
前端·javascript
一心赚狗粮的宇叔1 小时前
03.Node.js依赖包补充说明及React&Node.Js项目
前端·react.js·node.js
子春一1 小时前
Flutter for OpenHarmony:音律尺 - 基于Flutter的Web友好型节拍器开发与节奏可视化实现
前端·flutter
JarvanMo1 小时前
150万开发者“被偷家”!这两款浓眉大眼的 VS Code 插件竟然是间谍
前端
亿元程序员1 小时前
大佬,现在AI游戏开发教程那么多,你不搞点卖给大学生吗?
前端
未来龙皇小蓝1 小时前
RBAC前端架构-02:集成Vue Router、Vuex和Axios实现基本认证实现
前端·vue.js·架构
晓得迷路了2 小时前
栗子前端技术周刊第 116 期 - 2025 JS 状态调查结果、Babel 7.29.0、Vue Router 5...
前端·javascript·vue.js