尝试实现了国外大佬用Web做出来跨窗口渲染动画效果

先看个图

原地址:twitter.com/_nonfigurat...

今天大佬群里分享了一个视频,有个国外大佬用Web做出来一个可以跨多浏览器窗口实时互动的渲染动画效果,看评论说有用到了window.screenX``window.screenY以及localStorage做的。

搜了下api,看起来思路不是太复杂,而且是纯js,也不依赖第三方,开干!

整体思路就是先用canvas在每个窗口中心画出一个圆形,然后把自己的相对屏幕的坐标信息存在localStorage里,然后再从localStorage里获取其它窗口的坐标信息,然后在自己窗口里画出连接线以及其它窗口的圆。

先看下实现后的动画效果

本文只是提供个思路,把多窗口实时移动、互动效果实现出来了,其它银河系云图粒子效果、以及移动窗口时的阻尼缓冲效果,还需要其它大佬启发了。

知识点:

  1. window.requestAnimationFrame浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘,比如画动画。
  2. const { screenX, screenY } = window获取浏览器相对屏幕坐标。
  3. const barHeight = window.outerHeight - window.innerHeight获取浏览器body顶部地址栏高度
  4. canvas画图api。
  5. localStorage本地存储器,同一域名下的多页面共享。

先画个圆

html 复制代码
<body>
  <div id="alert1" style="white-space:pre-wrap;"></div>
  

  <div id="alert2" style="white-space:pre-wrap;"></div>
  <canvas id="canvas1" style="position:absolute;left:0;top:0;"></canvas>
</body>
javascript 复制代码
const alert1 = document.getElementById('alert1');
const alert2 = document.getElementById('alert2');
const canvas1 = document.getElementById('canvas1');
const ctx = canvas1.getContext('2d');
const color = 'red';

function draw() {
    const { clientWidth, clientHeight } = document.body; // 获取body高宽
    const { screenX, screenY } = window; // 获取浏览器相对屏幕坐标
    const barHeight = window.outerHeight - window.innerHeight; // 获取浏览器body顶部地址栏高度

    // 记录log
    alert1.textContent = JSON.stringify({ clientWidth, clientHeight, screenX, screenY, barHeight }, '', 2);

    // 设置canvas为整个body高宽,铺满body
    canvas1.width = clientWidth;
    canvas1.height = clientHeight;

    // 获取自己的圆心坐标,为body中心
    const x = clientWidth / 2;
    const y = clientHeight / 2;

    // 画圆
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(x, y, 15, 0, Math.PI * 2);
    ctx.fill();

    window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);

效果如下图

把当前窗口position信息存localStorage

key暂时用int 0,存相对屏幕的横纵坐标,以及颜色。

javascript 复制代码
const key = 0; // storage key
...

// 记录自己的position
const position = {
    top: y + barHeight + screenY, // 相对屏幕坐标
    left: x + screenX,
    color: color, // 自己的颜色
};

// 更新自己窗口的position
localStorage.setItem(key, JSON.stringify(position));

// 记录log
alert2.textContent = JSON.stringify(localStorage, '', 2);

效果如下图,可以看到storage存了一个"0"记录。

给不同窗口定义不同storage key

由于storage里得存多个窗口的圆心位置信息,所以得给不同窗口定义不同key值,规范来说应该弄个方法获取浏览器唯一标识,但是没有现成好用api,就暂时弄成自增index序号机制。

javascript 复制代码
const keys = getStorageKeys(); // 获取其它窗口的storage keys
const key = keys.length == 0 ? 1 : keys.at(-1) + 1; // 自增最大的key序号,定义自己窗口storage key
const color = ['red', 'blue', 'green'][key % 3]; // 获取圆颜色

// 窗口关闭时删除自己窗口storage
window.onunload = function () {
    localStorage.removeItem(key);
}

function getStorageKeys() {
    const keys = [];
    for (let i = 0; i < localStorage.length; i++) {
        const k = Number(localStorage.key(i));
        !isNaN(k) && keys.push(k);
    }
    return keys.sort((a, b) => a - b);
}

然后在把key值通过log打在页面上,先清空下storage:localStorage.clear(),然后运行两个窗口。 效果如下图,可以看到两个窗口都有自己的唯一key,storage里也存了对应position信息。

画出跟其它窗口圆心的连接线

思路是遍历其它窗口存在storage里的location,然后遍历,计算相对自己窗口圆心的横纵间距,最后画线。

javascript 复制代码
// 获取其它窗口position,并遍历
getStorageKeys().forEach(k => {
    const position2 = JSON.parse(localStorage.getItem(k)); // 获取其中一个窗口的圆心position
    const w = position2.left - position.left; // 获取相对自己圆心的横向间距
    const h = position2.top - position.top; // 获取相对自己圆心的纵向间距

    // 画连接线
    ctx.strokeStyle = "black";
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x + w, y + h);
    ctx.stroke();
})

开三个窗口,看看效果。

覆盖

当多窗口重叠覆盖时,原视频里会出现在顶层窗口里同时出现效果,如下图。 思路是,把其它圆也画在当前窗口里,也就是说需要在所有窗口里,画出所有圆,只是其它圆坐标会在窗口外面,所以在窗口里看不到而已。

javascript 复制代码
// 获取其它窗口position,并遍历
getStorageKeys().forEach(k => {
    const position2 = JSON.parse(localStorage.getItem(k)); // 获取其中一个窗口的圆心position
    const w = position2.left - position.left; // 获取相对自己圆心的横向间距
    const h = position2.top - position.top; // 获取相对自己圆心的纵向间距

    // 在自己的canvas上画出该圆
    ctx.fillStyle = position2.color;
    ctx.beginPath();
    ctx.arc(x + w, y + h, 15, 0, Math.PI * 2);
    ctx.fill();

    // 画连接线
    ctx.strokeStyle = "black";
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x + w, y + h);
    ctx.stroke();
})

看下效果,有两个窗口覆盖时,俩圆在最上层窗口同时显示了,但是这里有个bug,蓝色和绿色俩圆没有连接线,所以还需要画下其它窗口的连接线。

完整代码

javascript 复制代码
const alert1 = document.getElementById('alert1');
const alert2 = document.getElementById('alert2');
const canvas1 = document.getElementById('canvas1');
const ctx = canvas1.getContext('2d');

const keys = getOtherKeys(); // 获取其它窗口的storage keys
const key = keys.length == 0 ? 1 : keys.at(-1) + 1; // 自增最大的key序号,定义自己窗口storage key
const color = ['red', 'blue', 'green'][key % 3]; // 获取圆颜色

// 窗口关闭时删除自己窗口storage
window.onunload = function () {
    localStorage.removeItem(key);
}

function getOtherKeys() {
    const keys = [];
    for (let i = 0; i < localStorage.length; i++) {
        const k = Number(localStorage.key(i));
        !isNaN(k) && keys.push(k);
    }
    return keys.sort((a, b) => a - b);
}

function draw() {
    const { clientWidth, clientHeight } = document.body; // 获取body高宽
    const { screenX, screenY } = window; // 获取浏览器相对屏幕坐标
    const barHeight = window.outerHeight - window.innerHeight; // 获取浏览器body顶部地址栏高度

    // 记录log
    alert1.textContent = JSON.stringify({ key, clientWidth, clientHeight, screenX, screenY, barHeight }, '', 2);

    // 设置canvas为整个body高宽,铺满body
    canvas1.width = clientWidth;
    canvas1.height = clientHeight;

    // 获取自己的圆心坐标,为body中心
    const x = clientWidth / 2;
    const y = clientHeight / 2;

    // 画自己的圆
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(x, y, 15, 0, Math.PI * 2);
    ctx.fill();

    // 记录自己的position
    const position = {
        top: y + barHeight + screenY,
        left: x + screenX,
        color: color,
    };
    // 获取其它窗口position,并遍历
    getOtherKeys().forEach(k => {
        const position2 = JSON.parse(localStorage.getItem(k)); // 获取其中一个窗口的圆心position
        const w = position2.left - position.left; // 获取相对自己圆心的横向间距
        const h = position2.top - position.top; // 获取相对自己圆心的纵向间距

        // 在自己的canvas上画出该圆
        ctx.fillStyle = position2.color;
        ctx.beginPath();
        ctx.arc(x + w, y + h, 15, 0, Math.PI * 2);
        ctx.fill();

        // 画连接线
        ctx.strokeStyle = "black";
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + w, y + h);
        ctx.stroke();
    })

    // 更新自己窗口的position
    localStorage.setItem(key, JSON.stringify(position));

    // 记录log
    alert2.textContent = JSON.stringify(localStorage, '', 2);

    window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);

总结

本文只是简单的实现了下多窗口位置计算、介绍下相关window api、简单的画图方法、以及localStorage的用法。

还有待优化:

  1. 当有多个窗口覆盖时,还需要画下其它窗口的连接线。
  2. 优化算法,现在的循环算法还有很大优化空间,比如:
    1. 先把position存到storage里,然后遍历每个窗口position信息;
    2. 然后在每个窗口上画出所有圆,所有连接线(无论连接线是不是连自己的);
    3. 也就是说,每个窗口画的内容都是一样的,只是canvas的基础坐标位置不一样,都是自己的窗口中心点。
  3. 其它效果还没实现,比如云图粒子的动画、还有移动窗口时的阻尼缓冲动画效果。
相关推荐
lichong9514 小时前
【Flutter&Dart】 listView.builder例子二(14 /100)
android·javascript·flutter·api·postman·postapi·foxapi
破浪前行·吴5 小时前
【初体验】【学习】Web Component
前端·javascript·css·学习·html
染指悲剧7 小时前
vue实现虚拟列表滚动
前端·javascript·vue.js
浩浩测试一下9 小时前
Web渗透测试之XSS跨站脚本之JS输出 以及 什么是闭合标签 一篇文章给你说明白
前端·javascript·安全·web安全·网络安全·html·系统安全
前端搬运工X10 小时前
Object.keys 的原生 JS 类型之困
javascript·typescript
肖老师xy10 小时前
h5使用better scroll实现左右列表联动
前端·javascript·html
一路向北North10 小时前
关于easyui select多选下拉框重置后多余显示了逗号
前端·javascript·easyui
Libby博仙10 小时前
.net core 为什么使用 null!
javascript·c#·asp.net·.netcore
一水鉴天10 小时前
为AI聊天工具添加一个知识系统 之26 资源存储库和资源管理器
前端·javascript·easyui
hvinsion10 小时前
HTML 迷宫游戏
前端·游戏·html