web端如何突破浏览器性能瓶颈?

背景:目前项目(后台管理系统)使用vue2.x开发,大概已经4年左右了,绝大部分功能是没什么性能问题的,也就是说不会卡顿,但是项目中未来需要开发一些功能,类似:“博思白板”这类的思维导图,还有一些数据量比较大的甘特图,不同柱状之间有连接线,目前有两套方案(Flutter、Electron),

现在领导想往flutter上走(目前组长用flutter实现了一个功能,打包出来的pc客户端比web端性能看上去要好,给出的终极理由是pc端能够吃满内存,而web端收浏览器性能限制,理论上没啥问题),但是团队小伙伴都是vue线路,更倾向后者,如果整个系统用flutter重新开发的话,心里没底,毕竟我们只是部分功能可能会比较卡,后续哪些复杂的功能如果利用flutter没有达到逾期效果,其他基础功能也没做好的话,那么小组的基本盘都丢失了。

我的想法是利用 konvajs 去做哪些复杂的功能,但是也没这方面的经验,对这个库的性能也不知道如何,今天用ai生成了一个表格,10列,大约1300行的时候,页面就显示空白了,对应减少行数则可正常显示,当然可以考虑虚拟化,一次不渲染这么多元素,但是也从侧面观察到这个库的性能如何,所以想知道该如何突破浏览器的限制,或者如何使konvajs性能更加优秀?

konva.js 创建表格例子,当rows变量改为1300时(本人电脑),表格无法正常显示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Konva.js Table with Scrollbars</title>
    <script src="https://cdn.jsdelivr.net/npm/konva@8.0.3/konva.min.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }

        #container {
            width: 100%;  /* 容器宽度 */
            height: 100vh; /* 容器高度,设置为视口高度 */
            overflow: auto; /* 启用滚动条 */
            position: relative;
        }
    </style>
</head>
<body>
    <div id="container"></div>

    <script>
        // 设置表格参数
        const rows = 50;  // 更多的行数
        const cols = 10;  // 更多的列数
        const cellWidth = 150;
        const cellHeight = 50;
        const padding = 10;

        // 创建舞台
        const stage = new Konva.Stage({
            container: 'container', // 容器元素的ID
            width: cols * cellWidth + padding * 2, // 舞台宽度
            height: rows * cellHeight + padding * 2  // 舞台高度
        });

        // 创建图层
        const layer = new Konva.Layer();
        stage.add(layer);

        // 绘制表格单元格
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
                const rect = new Konva.Rect({
                    x: col * cellWidth + padding,
                    y: row * cellHeight + padding,
                    width: cellWidth,
                    height: cellHeight,
                    fill: '#fafafa',
                    stroke: '#333',
                    strokeWidth: 1
                });

                // 添加单元格点击事件
                rect.on('click', function () {
                    alert(`Cell clicked: Row ${row + 1}, Col ${col + 1}`);
                });

                // 在图层上添加单元格
                layer.add(rect);

                // 添加文本到单元格
                const text = new Konva.Text({
                    x: col * cellWidth + padding + 10,
                    y: row * cellHeight + padding + 10,
                    text: `R${row + 1}C${col + 1}`,
                    fontSize: 16,
                    fontFamily: 'Calibri',
                    fill: '#333'
                });

                layer.add(text);
            }
        }

        // 渲染图层
        layer.draw();
    </script>
</body>
</html>
阅读 299
2 个回答
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化的Konva.js表格</title>
    <script src="https://cdn.jsdelivr.net/npm/konva@8.0.3/konva.min.js"></script>
    <style>
        body { margin: 0; padding: 0; overflow: hidden; }
        #container {
            width: 100%;
            height: 100vh;
            overflow: auto;
            position: relative;
        }
    </style>
</head>
<body>
    <div id="container"></div>

    <script>
        // 表格参数
        const rows = 1300;  // 大量行
        const cols = 10;
        const cellWidth = 150;
        const cellHeight = 50;
        const padding = 10;
        
        // 创建舞台
        const stage = new Konva.Stage({
            container: 'container',
            width: cols * cellWidth + padding * 2,
            height: rows * cellHeight + padding * 2
        });
        
        // 创建图层
        const layer = new Konva.Layer();
        stage.add(layer);
        
        // 创建背景网格(单一形状,而不是多个矩形)
        const grid = new Konva.Shape({
            sceneFunc: function(context) {
                context.beginPath();
                // 绘制垂直线
                for (let i = 0; i <= cols; i++) {
                    const x = i * cellWidth + padding;
                    context.moveTo(x, padding);
                    context.lineTo(x, rows * cellHeight + padding);
                }
                // 绘制水平线
                for (let i = 0; i <= rows; i++) {
                    const y = i * cellHeight + padding;
                    context.moveTo(padding, y);
                    context.lineTo(cols * cellWidth + padding, y);
                }
                context.strokeStyle = '#ccc';
                context.lineWidth = 1;
                context.stroke();
            }
        });
        layer.add(grid);
        
        // 虚拟滚动实现
        const container = document.getElementById('container');
        let visibleCells = new Map(); // 跟踪可见单元格
        
        function renderVisibleArea() {
            // 获取滚动位置
            const scrollTop = container.scrollTop;
            const scrollLeft = container.scrollLeft;
            
            // 计算可见范围(添加缓冲区)
            const startRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 5);
            const endRow = Math.min(rows, Math.ceil((scrollTop + container.clientHeight) / cellHeight) + 5);
            const startCol = Math.max(0, Math.floor(scrollLeft / cellWidth) - 2);
            const endCol = Math.min(cols, Math.ceil((scrollLeft + container.clientWidth) / cellWidth) + 2);
            
            // 跟踪新的可见单元格
            const newVisibleCells = new Map();
            
            // 创建或更新可见单元格
            for (let row = startRow; row < endRow; row++) {
                for (let col = startCol; col < endCol; col++) {
                    const cellId = `${row}-${col}`;
                    
                    if (!visibleCells.has(cellId)) {
                        // 创建新单元格
                        const group = new Konva.Group({
                            x: col * cellWidth + padding,
                            y: row * cellHeight + padding,
                            width: cellWidth,
                            height: cellHeight,
                            id: cellId
                        });
                        
                        // 单元格背景
                        const rect = new Konva.Rect({
                            width: cellWidth,
                            height: cellHeight,
                            fill: '#fafafa',
                            stroke: '#333',
                            strokeWidth: 0.5
                        });
                        
                        // 单元格文本
                        const text = new Konva.Text({
                            x: 10,
                            y: 15,
                            text: `R${row + 1}C${col + 1}`,
                            fontSize: 16,
                            fontFamily: 'Calibri',
                            fill: '#333'
                        });
                        
                        group.add(rect);
                        group.add(text);
                        layer.add(group);
                        
                        // 添加点击事件
                        group.on('click', function() {
                            console.log(`Cell clicked: Row ${row + 1}, Col ${col + 1}`);
                        });
                        
                        newVisibleCells.set(cellId, group);
                    } else {
                        // 保留现有单元格
                        newVisibleCells.set(cellId, visibleCells.get(cellId));
                        visibleCells.delete(cellId);
                    }
                }
            }
            
            // 移除不再可见的单元格
            for (const [id, cell] of visibleCells.entries()) {
                cell.destroy();
            }
            
            // 更新可见单元格集合
            visibleCells = newVisibleCells;
            
            // 批量绘制(提高性能)
            layer.batchDraw();
        }
        
        // 监听滚动事件
        container.addEventListener('scroll', renderVisibleArea);
        
        // 初始渲染
        renderVisibleArea();
    </script>
</body>
</html>
  1. 你自己实现的底层逻辑,比如 canvas 代替 html 渲染,不管你用什么库,100% 没有现在的浏览器效率高
  2. 但是现在的浏览器并没有针对你们的场景做优化,比如你说的巨型表格,如果全部数据都渲染出来,然后使用原生的滚动和事件,肯定很慢。但你可以使用虚拟滚动条来做,即手动控制屏幕上露出的内容,只渲染总数据集中的一小部份。

    1. 针对大量数据的操作,可以放进 worker 去做。
  3. 用 flutter 我认为一样没优势。据我所知,flutter 并没有针对你们的场景做优化。
  4. figma 自己写 wasm 优化了数据处理
  5. 在线办公软件据我所知也是把自己实现的局部渲染
  6. 所以核心在于算法
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏