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中确保文本在图片之上显示的更多相关文章

  1. Vue 组件异步加载(懒加载)

    一.vue的编译模式 (1)路由配置信息 //eg1: const MSite = resolve => require.ensure([], () =>resolve(require([ ...

  2. vue 里面异步加载高德地图

    前言 关于Vue 里面使用异步加载高德地图 项目中其实只有几处需要用到地图,不需要全局引入 在index文件中引入js会明显拖慢首屏加载速度,虽然可以使用异步加载script的方式解决,但是始终觉得不 ...

  3. vue生命周期加载顺序

    1.beforeCreate(创建前)表示实例完全被创建出来之前,vue 实例的挂载元素$el和数据对象 data 都为 undefined,还未初始化.此钩子函数不能获取到数据,dom元素也没有渲染 ...

  4. 图解script的三种加载方式 异步加载顺序

    摘录如下: 可以很清晰的看出: <script>: 脚本的获取和执行是同步的.此过程中页面被阻塞,停止解析. <script defer = "defer"> ...

  5. vue swiper异步加载轮播图,并且懒加载

    参考:https://blog.csdn.net/weixin_38304202/article/details/78282826 效果: 此处安装省略 vue: <div class=&quo ...

  6. 当vue页面异步加载的数据想在页面上渲染怎么办

    <template> <div class="test"> <div v-for="(item, index) in arr" : ...

  7. Vue.js 子组件的异步加载及其生命周期控制

    前端开发社区的繁荣,造就了很多优秀的基于 MVVM 设计模式的框架,而组件化开发思想也越来越深入人心.这其中不得不提到 Vue.js 这个专注于 VM 层的框架. 本文主要对 Vue.js 组件化开发 ...

  8. 【转】【玩转cocos2d-x之二十三】多线程和同步03-图片异步加载

    原创作品,转载请标明:http://blog.csdn.net/jackystudio/article/details/15334159 cocos2d-x中和Android,Windows都 一样, ...

  9. IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)

    EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...

  10. 关于ios异步加载图片的几个开源项目

    一.HjCache  原文:http://www.markj.net/hjcache-iphone-image-cache/ 获取 HJCache: HJCache is up on github h ...

随机推荐

  1. 解决HtmlUnit执行JS报错提示ScriptException

    问题描述 HtmlUnit作为一款比Selenium更轻量的HeadLess的Java版本浏览器模拟器,不需要在服务器上安装部署浏览器及其Driver程序. 但是,众所周知,HtmlUnit对JS脚本 ...

  2. WebKist Inside: CSS 样式表的组成

    1 StyleSheet 一张 StyleSheet 由一系列 Rules 组成,这些 Rules 可以分成 2 大类: 1 Style Rule 2 At-Rule 下面的例子展示了 Style R ...

  3. Flutter 多线程实现

    异步Isolate实现多线程   最近在看Flutter开发相关知识点,对照着Android原生,探究了下多线程实现方式.在Flutter中有 Isolate,隔离,它的实现原理并不是内存共享的,它更 ...

  4. createRange表示文档中的一个范围——用于js判断文字添加省略号情况

    document.createRange() 是 JavaScript 中的一个方法,用于创建一个 Range 对象,表示文档中的一个范围.Range 对象通常用于选择文档中的一部分内容,然后对其进行 ...

  5. set 容器详解 附大根堆题解

    声明 本文中题解部分内容大部分转载自 @sonnety 的这篇博客中,本文为为方便复习而写的结论类文章,读者可自行跳转至原文处阅读. PART 1 set 什么是 set --来源cppreferen ...

  6. C语言:删除顺序表中重复的信息—(删除顺序表中重复的单词)

    如何删除顺序表中的重复单词: (开始看内容之前容朕说一句:如果你最后怎么都运行不了你想要的结果,①我敢保证大概率是你的下标越界你的下标越界了你的下标越界了.②在我这程序里面你肯定打少了p--,少了p- ...

  7. nginx接受请求连接事件模块流程

    操作系统内核: 三次握手,当用户发来一个 SYN 报文时,系统内核会返回一个SYN+ACK确认给客户端,当客户端再次发送ACK来的时候,此时就已经建立了三次握手. 完成三次握手后,操作系统会根据系统内 ...

  8. MySQL学习笔记-数据定义语言

    SQL-数据定义语言(DDL) 一.操作数据库 1.查询 # 查询所有数据库 show databases; # 查询当前数据库 select database(); 2.创建 create data ...

  9. pandas基础--层次化索引

    pandas含有是数据分析工作变得更快更简单的高级数据结构和操作工具,是基于numpy构建的. 本章节的代码引入pandas约定为:import pandas as pd,另外import numpy ...

  10. Java异常中throw 与throws的区别

    throw 与 throws区别 在Java中,throws和throw是两个不同的关键字,它们在异常处理中起着不同的作用. throws关键字: throws用于声明一个方法可能会抛出的异常.当一个 ...