A recent demand for mobile end generated pictures and upload, stepped on a lot of pits, recorded here. Since this function is mainly focused on the use of canvas to draw images, and generate web / upload pictures, so this mostly pictures and related records.
Drawing section
onload
Initially when using canvas directly load the network picture, forget to consider the problem of image loading, and thus directly on the handwriting image.src = xxx
followed by ctx.drawImage
the last picture did not find the network is drawn up, think of it before you can use pictures must first be loaded completely ctx.drawImage
go drawing, otherwise the picture is not finished loading, canvas
direct draw a blank picture.
export class Canvas {
// code here...
// 加载图片
addImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
}
img.src = src;
});
}
// code here...
}
复制代码
Picture a cross-domain issues
When you're done, run the code originally thought this up, but found that due to the visit of the CDN picture address, Image by default does not support cross-domain access to resources must be manually specify crossOrigin
attributes can access cross-domain images.
export class Canvas {
// code here...
// 加载图片
addImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
resolve(img);
}
img.src = src;
});
}
// code here...
}
复制代码
Rendering hierarchy
After the image is loaded up, feeling had been resolved, but found that I was supposed to render three pictures, and finally came out of a background image, drawing the other two are gone, but the print log can be seen in the code inside canvas indeed carried out these two pictures rendering logic.
Search of the investigation found that information only in accordance with the hierarchical relationship canvas rendering the order to show the drawing can not be specified levels manually, so we want to draw the picture above background, it is necessary to wait until the background drawing is completed before proceeding to the other logic.
export class Canvas {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
constructor() {
// other code
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d')!;
// other code
}
// code here...
addImage(src: string) {
// ...
}
drawImage() {
const bg = 'https://xxx';
this.addImage(bg).then((img: ImageBitmap) => {
this.ctx.drawImage(img, 0, 0, img.width, img.height);
// 在这之后才能继续绘制其他图片
this.addImage(xxx);
});
}
}
复制代码
Draw a circle picture
After all drawing is over, the product suddenly to me and says, want to add in the final surface area of a line of user information, including user avatar and user name, originally I thought it was a very simple task, in accordance with the above logic and then draw a picture a text can be later found user avatar pictures are square, but the product hope to be a round avatar picture, in css
need only a simple line border-radius: 50%
of something that makes me a headache.
After trying a variety of approaches have failed, checked the Internet to find information I realized that context
there are save and restore methods, coupled with the clip
cutting you can complete a picture of the round!
export class Canvas {
constructor() {
// ...
}
addImage() {
// ...
}
drawImage() {
// ...
}
drawUserInfo() {
// other code
const src = 'https://xxx';
this.addImage(src).then((img: ImageBitmap) => {
this.ctx.save();
this.ctx.arc(x, y, r, 0, Math.PI * 2);
this.ctx.clip();
this.ctx.drawImage(img, x, y, img.width, img.height);
this.ctx.restore();
});
}
}
复制代码
So first save the current state of the canvas, and then arc
draw a circle in the position of the avatar picture, then cut off the excess part, then draw the picture, and finally restore the canvas state can be.
Fixes
3x map
After the above steps will be finished, I tested CDN draw pictures and upload function, everything is normal!
Then, when this picture show full of joy, and found to show up on the phone is too vague, and even the words are not clear.
Then I realized we usually use pictures are 2x or 3x map, and now I follow the UI draft of 360px
this picture drawn only 1x width drawings to show up on our high-resolution mobile phone will be very vague, and therefore to make the picture is not blurred, and I need the picture becomes 3x map.
Thus, when drawing all of the width and height of the array and all other × 3.
this.width = 360 * 3;
this.height = 500 * 3;
this.ctx.drawImage(bg, 0, 0, this.width * 3, this.height * 3);
// 其他改动同理
复制代码
After this change, showing out of the picture is very clear!
Cached images lead to the main draw card
After due to the need to load multiple pictures, so here I need to listen and draw all the pictures are loaded successfully, you can perform the ultimate canvas.toBlob()
logic and upload pictures.
export class Canvas {
constructor() {
// ...
this.loadedImageNumber = 0;
}
// code here
imageOnLoaded() {
this.loadedImageNumber++;
if(this.laodedImageNumber >= 3) {
// upload image
}
}
}
复制代码
Pm when the results of the test and qa students, often found stuck upload process, has been in a loading state, and later after numerous attempts and troubleshoot problems, because some found in front of the picture has been loaded once, the picture is possible here from the browser which get's cache, and therefore will not perform the onload
function, thus leading to this.loadedImageNumber
have failed to reach 3 greater than or equal conditions, so it stuck in the loading state.
The solution is to look perfect addImage
function, listening onload
simultaneously to determine what img the state, if it is complete
then also survive a callback logic, by the way also add a bit about the onerror
process.
export class Canvas {
// code here...
// 加载图片
addImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
}
img.onerror = () => {
// error callback
reject();
}
img.src = src;
if(img.complete) {
resolve(img);
}
});
}
// code here...
}
复制代码
Such test it, all pictures can be properly loaded out ~
polyfill
After the above problem has been solved, QA feedback students have some low-end phones are still stuck in the loading state, I would have thought it was the picture drawn still have problems, then I borrowed cell phone to debugging a bit and found the card in the picture is not drawing process but canvas.toBlob()
when given, so I stuck behind the logic.
export class Canvas {
// code here
imageOnLoaded() {
this.loadedImageNumber++;
if(this.laodedImageNumber >= 3) {
this.canvas.toBlob(blob => {
// 真正的上传函数
this.uploadImage(blob);
},
'image/jpeg',
1.0
)
}
}
// code here
}
复制代码
Once again I checked the Internet to find information, find the need to fight polyfill job.
github地址:JavaScript-Canvas-to-Blob
readme many online article describes the use of, or look directly github official website is also very easy to understand.
if(__BROWSER__) {
// import canvas toblob polyfill
require('blueimp-canvas-to-blob');
}
export class Canvas {
// ...
}
复制代码
__BROWSER__
It is webpack.DefinePlugin
defined by the client rendering environment.
This time should be feeling everything will be fine.
Optimizing image size
Then again QA students looking for. .
QA feedback classmate said image upload too slow, weak net case for a long time before the end of loading, or even directly to the back-end interface to return a timeout has not yet ended upload pictures.
I looked at the picture upload packet capture interface, I found a bit slow, since the resulting picture too bulky, and more than a full 2.6M
Asked about his colleagues, it turned out to be the first picture fuzzy, I want to improve the picture quality, while the 3x change map again toBlob()
when the picture quality is designated 1.0, resulting in a picture too large.
After the picture quality here is designated as a volume of about 0.8 about to come down, and the picture quality is actually not changed much.
export class Canvas {
// code here
imageOnLoaded() {
this.loadedImageNumber++;
if(this.laodedImageNumber >= 3) {
this.canvas.toBlob(blob => {
// 真正的上传函数
this.uploadImage(blob);
},
'image/jpeg',
0.8
)
}
}
// code here
}
复制代码
This feature is finally here to complete OwO
Reproduced in: https: //juejin.im/post/5cecb0b36fb9a07ee565feea