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. 如何在 Ubuntu 服务器上安装桌面环境 (GUI)

    先以VNC方式远程登录服务器 执行命令 sudo apt update && sudo apt upgrade # 选择1---使用tasksel安装 sudo apt install ...

  2. java如何将JSONObject转成实体对象

    import com.google.gson.Gson; import org.json.JSONObject; // ... JSONObject json = new JSONObject(&qu ...

  3. 关于armcc中static __inline修饰符的记录

    相关的知识点:内联函数,static关键字 在一次stm32的库代码分析中发现static __inline 的函数定义,对此有些疑惑,static和inline两个关键字为什么要进行连用呢? 对此进 ...

  4. IPv6 — 移动性

    目录 文章目录 目录 前文列表 IPv6 的移动性 移动操作 路由优化 前文列表 <IPv6 - 网际协议第 6 版> <IPv6 - 地址格式与寻址模式> <IPv6 ...

  5. OpenNESS NTS 的前世今生

    目录 文章目录 目录 NEV SDK NEV SDK 的软件架构 NEV SDK 的持续演进 OpenNESS NTS NTS 在 VM 场景中的应用 NTS 在 Container 场景中的应用 N ...

  6. CodePen 的国内替代「笔.COOL」,一个功能完备、使用便捷的在线HTML/CSS/JS编辑器和作品分享平台

    笔.COOL,是一个最近在国内崭露头角的在线HTML/CSS/JS编辑器和作品分享平台. 笔.COOL 提供了一个在线的 HTML.CSS 和 JavaScript 代码编辑器.无需任何安装,你只需打 ...

  7. Istio(八):istio安全之认证,启用mTLS

    目录 一.模块概览 二.系统环境 三.istio认证 3.1 证书创建与轮换 3.2 对等认证和请求认证 3.2.1 对等认证 3.2.2 请求认证 3.3 mTLS 3.3.1 双向 TLS 3.3 ...

  8. SuperSocket AppServer.NewRequestReceived 不触发 接受不到字节

    SuperSocket AppServer.NewRequestReceived 不触发 接受不到字节 针对.netframework版本 1.6.* 使用 FixedHeaderReceiveFil ...

  9. redis RDB AOF数据持久化

    目录 redis RDB持久化[手工持久化]: redis RDB持久化条件配置[适合用于备份]redis rdb持久化策略 redis AOF持久化 redis AOF持久化配置 redis RDB ...

  10. 在js、vue中使用方法调用a标签

    <el-button type="primary" style="width:130px" @click="shout"> &l ...