Vue 处理异步加载顺序问题之如何在Konva中确保文本在图片之上显示
作者:Dandelion
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}`); }); }
然而,这种方式下,我们遇到了无法传递参数的问题。在回调函数中,无法访问循环中的变量geometry
和group
。
尝试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
声明来确保每次循环迭代中创建一个新的块作用域,变量捕获是正确的。
主要区别
变量捕获:
- 第一种写法通过IIFE来捕获当前循环迭代中的变量状态,确保异步回调中使用的是当前迭代的值。
- 第二种写法直接传递回调,如果使用
let
声明或其他方法,能正确捕获每次迭代中的变量状态。
代码可读性:
第一种写法较为复杂,但能够确保异步回调中的变量正确捕获当前状态。
第二种写法更简洁,但需要确保使用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 异步加载顺序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!