Nanfeng

Notes on software development, code, and curious ideas

Loading a Local Image in a Cocos Creator Web Build

Cocos Creator version: 3.7.2.

A web game or app may need to let users upload a local image for an avatar or preview. Cocos Creator does not provide a native browser file picker, so this implementation combines an HTML <input type="file"> with TypeScript.

Create a hidden image picker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onLoad() {
this.createImagePicker();
}

createImagePicker() {
this.inputElement = document.createElement('input');
this.inputElement.type = 'file';
this.inputElement.accept = 'image/*';
this.inputElement.style.display = 'none';
document.body.appendChild(this.inputElement);
this.inputElement.addEventListener('change', this.handleFileChange);
}

private handleFileChange = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
this.handleFileUpload(target.files[0]);
}
}

Open the picker

The button handler triggers the hidden input’s click event. Selecting a file then fires change and calls handleFileUpload.

1
2
3
HandlerBtn_selectImg() {
this.inputElement.click();
}

Read and optionally compress the image

FileReader converts the selected image into a Base64 data URL. Large images may fail to upload, so this example compresses them first. Compression is optional.

1
2
3
4
5
6
7
8
9
10
private handleFileUpload(file: File) {
const reader = new FileReader();
reader.onload = (e) => {
const imageUrl = e.target?.result as string;
Tool.compressImageTo200KB(imageUrl).then((compressedImageUrl) => {
console.log(compressedImageUrl);
});
};
reader.readAsDataURL(file);
}

Display it in Cocos Creator

1
2
3
4
5
6
7
8
9
10
11
12
13
public static loadTextureInCocos(node: Node, imageUrl: string, width, height) {
const image = new Image();
image.onload = () => {
const img = new ImageAsset(image);
const texture = new Texture2D();
texture.image = img;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
node.getComponent(Sprite).spriteFrame = spriteFrame;
node.getComponent(UITransform).setContentSize(width, height);
};
image.src = imageUrl;
}

Remove the DOM node and event listener

Always remove the input when the component is destroyed. Otherwise document.body and the event listener continue referencing it, preventing garbage collection and potentially causing unexpected behavior later.

1
2
3
4
5
6
7
onDestroy() {
if (this.inputElement) {
this.inputElement.removeEventListener('change', this.handleFileChange);
this.inputElement.remove();
this.inputElement = null;
}
}

Convert the image for upload

This converts a Base64 data URL to Uint8Array, then to a number[] for transmission. The resulting array can be very large.

1
2
3
4
5
6
private async base64ToUint8Array(base64DataURL: string) {
const res = await fetch(base64DataURL);
const blob = await res.blob();
const arrayBuffer = await blob.arrayBuffer();
return Array.from(new Uint8Array(arrayBuffer));
}
+