Vue 处理异步加载顺序问题:在Konva中确保文本在图片之上显示

vue,konva · 浏览次数 : 54

小编点评

本文讨论了在Vue项目中使用Konva库处理异步加载顺序的问题,特别是在绘制文本在图片之上时的挑战。文章首先指出,由于图像加载是异步的,文本有时会显示在图片下方。然后,文章提出了一种新的实现方式,该方法将回调函数放在imageObj.onload内部,并使用let声明来确保每次循环迭代中创建一个新的块作用域,从而正确捕获每次迭代中的变量状态。 1. **异步问题**: - 描述了在Vue项目中使用Konva时可能遇到的异步加载顺序问题。 - 提出了在回调函数中传递参数的问题,以及直接回调和在函数中写回调的尝试。 2. **解决方案**: - 介绍了通过立即执行函数表达式(IIFE)和使用let声明来捕获循环中的变量状态。 - 讨论了使用var和let声明的区别,以及它们在异步回调中的作用。 3. **代码示例**: - 提供了两种不同的实现方式,并解释了它们的优缺点。 - 使用了IIFE和let声明来确保异步回调中的变量是当前迭代的值。 总的来说,确保在图像加载完成后再添加其他元素是处理异步加载顺序问题的关键。通过将回调函数放在imageObj.onload中,并正确处理变量捕获,我们可以确保文本总是绘制在图片之上。

正文

Vue 处理异步加载顺序问题:在Konva中确保文本在Konva之上显示

在使用Konva开发应用时,我们经常会遇到需要将文本绘制在图片之上的情况。一个常见的问题是,由于图像加载是异步的,文本有时会显示在图片下方。这篇博客将总结如何正确处理这种异步加载顺序问题。

我之前写过一篇博客,主要是为了说明如何通过父子组件来控制Konva组件间的先后绘制顺序,利用了Vue的生命周期(自定义父子组件mounted执行顺序)。这种方法适用于将Konva组件分开到不同的Vue组件中,通过Vue的生命周期来确保正确的绘制顺序。然而,这种方法并不适用于所有情况,比如说必须在同一个文件中编写,一起调用,或者都在setup语法糖中等情况。本文将探讨在这些情况下如何确保文本在图片之上显示。

问题描述

我们希望在绘制图片后,再在图片上方绘制文本。一个简单的代码片段如下:

for (let i = 0; i < 4; i++) {
    const geometry = {
        x: 100 + (i % 2 === 0 ? 0 : 150),
        y: 50 + (i < 2 ? 0 : 100),
        width: 100,
        height: 100,
    };

    addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
        addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
    });
}

初步实现

我们实现了两个函数:addWidgetImgToLayer用于加载图片,addShapesToLayer用于添加Shapes(这里添加了Text)。

const addWidgetImgToLayer = (geometry, src, listening = false, parent, eventType, eventFunc, callback) => {
    const layer = konvaStore.layers['consoleLayer'];
    const imageObj = new Image();
    let konvaImage = null;

    imageObj.onload = () => {
        konvaImage = new Konva.Image({
            ...geometry,
            image: imageObj,
            listening: listening,
        });
        if (eventType && eventFunc) {
            konvaImage.on(eventType, eventFunc);
        }
        if (parent) {
            parent.add(konvaImage);
        } else {
            layer.add(konvaImage);
        }
        layer.batchDraw();
        if (callback) {
            callback();
        }
    };
    imageObj.src = src;
    return imageObj;
};

const addShapesToLayer = (geometry, type, listening = false, parent, text) => {
    const layer = konvaStore.layers['consoleLayer'];
    let shape = null;
    if (type === 'text') {
        shape = new Konva.Text({
            ...geometry,
            listening: listening,
            text: text,
            fontSize: 20,
            align: 'center',
            verticalAlign: 'middle',
        });
    }
    if (parent) {
        parent.add(shape);
    } else {
        layer.add(shape);
    }
    layer.batchDraw();
    return shape;
};

异步问题

这里的关键在于imageObj.onload回调函数。图片加载是异步的,代码不会等待图片加载完成才执行接下来的语句。因此,必须确保回调函数在图片加载完成后才执行添加文本的操作。

尝试1:直接回调

我们首先尝试使用回调来确保顺序执行:

for (let i = 0; i < 4; i++) {
    const geometry = {
        x: 100 + (i % 2 === 0 ? 0 : 150),
        y: 50 + (i < 2 ? 0 : 100),
        width: 100,
        height: 100,
    };

    addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
        addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
    });
}

然而,这种方式下,我们遇到了无法传递参数的问题。在回调函数中,无法访问循环中的变量geometrygroup

尝试2:在函数中写回调

我们尝试在addWidgetImgToLayer函数中编写回调,但放在了imageObj.onload之外:

const addWidgetImgToLayer = (geometry, src, listening = false, parent, eventType, eventFunc, callback) => {
    const layer = konvaStore.layers['consoleLayer'];
    const imageObj = new Image();
    let konvaImage = null;

    imageObj.onload = () => {
        konvaImage = new Konva.Image({
            ...geometry,
            image: imageObj,
            listening: listening,
        });
        if (eventType && eventFunc) {
            konvaImage.on(eventType, eventFunc);
        }
        if (parent) {
            parent.add(konvaImage);
        } else {
            layer.add(konvaImage);
        }
        layer.batchDraw();
    };
    
    if (callback) {
        callback();
    }
    
    imageObj.src = src;
    return imageObj;
};

这导致了文本依然在图片下方的问题,因为回调函数立即执行,而不是等待图片加载完成再执行。分析发现,问题依然是异步加载的问题,即使解决了参数传递问题,这种方法也不能正确得到想要的结果。

尝试3:将回调移至imageObj.onload内部

我们意识到了JavaScript的异步执行机制。在函数嵌套的情况下,异步函数会在调用堆栈清空后才执行。我们需要确保回调函数在图片加载完成后才执行。

const addWidgetImgToLayer = (geometry, src, listening = false, parent, eventType, eventFunc, callback) => {
    const layer = konvaStore.layers['consoleLayer'];
    const imageObj = new Image();
    let konvaImage = null;

    imageObj.onload = () => {
        konvaImage = new Konva.Image({
            ...geometry,
            image: imageObj,
            listening: listening,
        });
        if (eventType && eventFunc) {
            konvaImage.on(eventType, eventFunc);
        }
        if (parent) {
            parent.add(konvaImage);
        } else {
            layer.add(konvaImage);
        }
        layer.batchDraw();
        
        if (callback) {
            callback();
        }
    };
    imageObj.src = src;
    return imageObj;
};

// 使用 addWidgetImgToLayer 并确保回调在图片加载完成后执行
for (let i = 0; i < 4; i++) {
    const geometry = {
        x: 100 + (i % 2 === 0 ? 0 : 150),
        y: 50 + (i < 2 ? 0 : 100),
        width: 100,
        height: 100,
    };

    // 这里可以使用两种不同的实现方式,现在是比较简洁的格式,下面会详细说明
    addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
        addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
    });
}

比较两种实现方式

在解决这个问题时,我们还可以采用两种不同的实现方式:

方式1:使用立即执行函数表达式(IIFE)

addWidgetImgToLayer(dispBtnGeometrys[i], './img/b.png', true, group, 'click', () => { }, (function (geo, grp, idx) {
    return function () {
        addShapesToLayer(geo, 'text', false, grp, `${idx}`);
    };
})(dispBtnGeometrys[i], group, i + 1));

这种写法使用了一个立即执行函数表达式(IIFE),这个函数会立即执行并返回一个新的函数。通过这种方式,可以捕获循环中的当前变量状态,并在异步回调中使用。

方式2:直接传递回调

for (let i = 0; i < 4; i++) {
    let geometry = {
        x: 100 + (i % 2 === 0 ? 0 : 150),
        y: 50 + (i < 2 ? 0 : 100),
        width: 100,
        height: 100,
    };

    addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
        addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
    });
}

这种写法直接传递了一个回调函数给addWidgetImgToLayer。通过使用let声明来确保每次循环迭代中创建一个新的块作用域,变量捕获是正确的。

主要区别

  1. 变量捕获

    • 第一种写法通过IIFE来捕获当前循环迭代中的变量状态,确保异步回调中使用的是当前迭代的值。
    • 第二种写法直接传递回调,如果使用let声明或其他方法,能正确捕获每次迭代中的变量状态。
  2. 代码可读性

    • 第一种写法较为复杂,但能够确保异步回调中的变量正确捕获当前状态。

    • 第二种写法更简洁,但需要确保使用let声明或其他方法正确捕获变量,而不能使用var(此处使用到的是循环变量i)。

      在JavaScript中,var声明的变量在函数作用域内是共享的,这意味着在异步回调中,它们的值可能会变成最后一次迭代的值。这会导致我们期望的结果不正确。而let声明的变量在每次循环迭代中都会创建一个新的块作用域,从而确保异步回调中捕获的是当前迭代的值。

      示例解释

      使用 var 声明的问题

      for (var i = 0; i < 4; i++) {
          const geometry = {
              x: 100 + (i % 2 === 0 ? 0 : 150),
              y: 50 + (i < 2 ? 0 : 100),
              width: 100,
              height: 100,
          };
      
          addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
              console.log(i);  // 可能会输出4,而不是期望的0, 1, 2, 3,可能在其他位置被修改过了,它们指向同一个地址
              addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
          });
      }
      

      因为var声明的变量i在函数作用域内是共享的(有点像Python),所以在异步回调中,i的值可能是循环结束时的值(4),而不是期望的0, 1, 2, 3。

      使用 let 声明解决问题

      for (let i = 0; i < 4; i++) {
          let geometry = {
              x: 100 + (i % 2 === 0 ? 0 : 150),
              y: 50 + (i < 2 ? 0 : 100),
              width: 100,
              height: 100,
          };
      
          addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
              console.log(i);  // 输出期望的0, 1, 2, 3,使用let声明变量不会出现问题
              addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
          });
      }
      

      let声明的变量i在每次循环迭代中都会创建一个新的块作用域,因此在异步回调中,i的值是当前迭代的值,确保输出的是期望的0, 1, 2, 3。

      其他捕获变量的方法

      另一种确保变量捕获正确的方法是使用立即执行函数表达式(IIFE):

      for (var i = 0; i < 4; i++) {
          (function(i) {
              let geometry = {
                  x: 100 + (i % 2 === 0 ? 0 : 150),
                  y: 50 + (i < 2 ? 0 : 100),
                  width: 100,
                  height: 100,
              };
      
              addWidgetImgToLayer(geometry, './img/b.png', true, group, 'click', () => {}, () => {
                  console.log(i);  // 输出期望的0, 1, 2, 3,可以理解为使用IIFE的话开辟空间事会先对使用到的数据进行了值拷贝
                  addShapesToLayer(geometry, 'text', false, group, `${i + 1}`);
              });
          })(i);
      }
      

      通过IIFE,每次循环迭代都会创建一个新的函数作用域,确保异步回调中的变量是当前迭代的值。

      结论

      在第二种写法中,通过使用let声明或IIFE,确保异步回调函数中捕获的变量值是当前迭代的值,而不是循环结束后的值。这是确保变量正确捕获和代码正确执行的关键。

总结

在处理Konva中的异步加载顺序问题时,确保在图像加载完成后再添加其他元素是关键。通过将回调函数放在imageObj.onload中,并正确处理变量捕获,我们可以确保文本总是绘制在图片之上。这不仅解决了显示顺序的问题,也为未来的调试提供了明确的方向。

与Vue 处理异步加载顺序问题:在Konva中确保文本在图片之上显示相似的内容:

Vue 处理异步加载顺序问题:在Konva中确保文本在图片之上显示

Vue 处理异步加载顺序问题:在Konva中确保文本在Konva之上显示 在使用Konva开发应用时,我们经常会遇到需要将文本绘制在图片之上的情况。一个常见的问题是,由于图像加载是异步的,文本有时会显示在图片下方。这篇博客将总结如何正确处理这种异步加载顺序问题。 我之前写过一篇博客,主要是为了说明如

Springboot简单功能示例-1 实现基本WEB服务

博主尝试通过gitee的发行版,使用Springboot为基础框架,逐步整合JWT、JPA、VUE等常用功能项目。 其中博主还将尝试统一异常处理、自定义加密认证、代码自动生成等功能

Vue 中 Promise 的then方法异步使用及async/await 异步使用总结

转载请注明出处: 1.Promise 的 then 方法使用 then 方法是 Promise 中 处理的是异步调用,异步调用是非阻塞式的,在调用的时候并不知道它什么时候结束,也就不会等到他返回一个有效数据之后再进行下一步处理; 想了解 Promise 的使用,可以看这篇文章: ES6 中 Prom

如何优化 Vue.js 应用程序

单页面应用(SPAs)当处理实时、异步数据时,可以提供丰富的、可交互的用户体验。但它们也可能很重,很臃肿,而且性能很差。在这篇文章中,我们将介绍一些前端优化技巧,以保持我们的Vue应用程序相对精简,并且只在需要的时候提供必需的JS。 注意:这里假设你对Vue和Composition API有一定的熟

『手撕Vue-CLI』处理不同指令

前言 在上一篇『手撕Vue-CLI』添加自定义指令中,已经实现了自定义指令的添加,但是指令还是比较简单的,只是简单的打印一句话,那么在实际运用场景中,可能会有更多的需求,比如可能需要在指令中传递参数,或者需要在指令中进行一些复杂的操作,那么这个时候我们就需要对指令进行处理了。 创建指令处理文件 在上

『手撕Vue-CLI』拉取模板名称

前言 好,经过上篇文章的介绍,已经可以有处理不同指令的能力了,接下来我们就来处理 vue create 指令,这个指令的本质就是从网络上下载提前准备好的模板,然后再自动安装模板中相关依赖。 所以实现 create 指令分为两步: 下载指定模板 安装模板中的依赖 先来看看官方的吧,我在终端中已经输入了

vue中key使用的问题

前言 在vue要求在遍历的时候最好加上key,在使用过程中总有些疑问,在这里做下分析 1.不使用key的时候vue是怎么处理的 在vue2.x文档中有如下描述 key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key

Vue 3 后端错误消息处理范例

前端如何存储处理后端返回的错误信息,并按不同来源绑定到页面,例如显示在不同输入框的周围。这样即可实现清晰的错误显示。

终于搞懂了!原来 Vue 3 的 generate 是这样生成 render 函数的

前言 在之前的 面试官:来说说vue3是怎么处理内置的v-for、v-model等指令? 文章中讲了transform阶段处理完v-for、v-model等指令后,会生成一棵javascript AST抽象语法树。这篇文章我们来接着讲generate阶段是如何根据这棵javascript AST抽象

Vue第三方库与插件实战手册

这篇文章介绍了如何在Vue框架中实现数据的高效验证与处理,以及如何集成ECharts、D3.js、Chart.js等图表库优化数据可视化效果。同时,探讨了Progressive Web App(PWA)的接入与优化策略,以提升Web应用的用户体验与加载速度。