TNTWeb – The full name of Tencent News Zhongtai front-end team, partners in the group have practiced and accumulated experience in Web front-end, NodeJS development, UI design, mobile APP and other large front-end fields.
At present, the team mainly supports the front-end development of Tencent news business. Besides business development, some front-end infrastructure has been accumulated to enable business efficiency improvement and product innovation.
The team advocates open source construction, has a variety of technical masters, the team Github address: github.com/tnfe
Author of this article pocket team
Summary of picture editor development
Background:
In the actual business development encountered picture editing module, actually picture editing itself is relatively decoupled from business module. For the user needs to provide the original map loading interface and export file interface after editing!
Therefore, it is thought that we can extract a picture editor component with complete functions and reduce user configuration, which is convenient for subsequent business use. There are already some components on the market that work just fine. The development of picture editing component is mainly based on the following two aspects:
1, need to be able to meet the common picture editing needs, expand more functions, etc
2. Good interaction is required
As with any component or module development, start by researching existing picture editors on the market; After comparison, I finally chose github.com/nhn/tui.ima… React support won’t be available until February 2021, and it doesn’t support hooks. We wrote our own editor based on ts+ React hooks. If you do not often use Canvas, you need to find a lot of APIS, and this is also the case for the author. While looking at the source code, I also look at the API of Canvas. The following is the basic implementation principle, including summary of common methods of Canvas, practice of editor and some functions to be implemented in the future.
Implementation of the basic principle canvas method
Before we begin, a few notes on canvas:
Labels are not available
Unlike many HTML tags, elements require a closing tag (), which you can’t write directly
All of your actions are in the render context
The < canvas > element creates a fixed-size canvas that is initially blank and can be used to draw and manipulate the content to be displayed through its rendering context.
The general steps of the picture editor are: the first step is to set the canvas and get the rendering context; the second step is to draw the picture on the canvas; the third step is to do various operations;
Step 1: Set up the canvas and get the render context:
<html>
<head>
<title>Canvas tutorial</title>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext('2d'); }}</script>
<style type="text/css">
canvas { border: 1px solid black; }
</style>
</head>
<body onload="draw();">
<canvas id="tutorial" width="150" height="150"></canvas>
</body>
</html>
Copy the code
Step 2: Draw the image on the Canvas
I’ve taken the source code for loadImage in tuI, and you can see that the drawImage method starts drawing at the origin of the canvas, and sets the size of the canvas to be equal to the size of the original canvas, which makes it very convenient for all of the subsequent operations because everything on the image is the same as the canvas, At the same time, it does not need to do special processing when exporting pictures. The disadvantage is that it is difficult to synthesize multiple pictures.
loadImage(url, callback, options) {
const img = new Image();
if (options && options.corsenabled) {
img.crossOrigin = 'Anonymous';
}
img.src = url;
img.onload = function () {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d');
context.drawImage(img, 0.0);
callback(canvas);
};
}
Copy the code
Step 3: Start manipulating the canvas
Take a few common major operations, drawing, rotating, cropping
Draw a triangle
The first step in drawing a triangle to generate a path is called beginPath(). Essentially, a path is made up of many subpaths, all in a list, all of which (lines, arcs, etc.) make up the graph. And every time this method is called, the list is cleared and reset, and we can redraw the new graph
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75.50);
ctx.lineTo(100.75);
ctx.lineTo(100.25); ctx.fill(); }}Copy the code
Rotate 90 degrees
Before I get into rotation, I’ll introduce you to two essential methods when you start drawing complex shapes.
Save () saves all the state of the canvas
The save and restore methods are used to save and restore the canvas state and take no arguments. The Canvas state is a snapshot of all the styles and distortions applied to the current screen.
My heart skipped a beat when I saw the two apis, ahem, undo and restore with this, and the history function is also implemented, however, not quite!! This brings us to a very important canvas noun: state. State is a word often heard by front-end students, state management, stateful component, stateless component and so on. There are also states in canvas. For example, when we call fillRect to draw the distance, the width of the rectangle side, the color of the side and so on are all states we have set in advance. Every time the save() method is called, the current state is pushed to the stack for saving. What properties and operations can be saved in the state? (ðŸ˜, some properties and operations are not stored in the state stack, which is why undo and some parts of the history record are missing), there are three types:
- Deformation of the current application (i.e. move, rotate, and scale, see below)
- And the following attributes: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
- Current Clipping Path
You can call the save method as many times as you want. Each time the restore method is called, the last saved state is popped from the stack and all Settings are restored.
Note that the rotation starts at the top left corner of the canvas, so if you want to rotate around the center of the canvas, you’ll need CTX. Translate (x, y) –x is the left/right offset, y is the up/down offset, as shown below:
Rotate 90 degrees ctx.rotate(math.pi);
Cut the clip
The essence of clipping is to hide what is not in the clipping path
Here are the before and after pictures and code
ctx.fillRect(0.0.150.150); // Initial style (draw state) and draw rectangle
ctx.translate(75.75);
ctx.beginPath();
ctx.arc(0.0.60.0.Math.PI*2.true);
ctx.clip(); // This line of code follows the clip
var lingrad = ctx.createLinearGradient(0, -75.0.75); CTX. CreateLinearGradient (x0, y0, x1, y1);
lingrad.addColorStop(0.'red');
lingrad.addColorStop(1.'# 143778');
ctx.fillStyle = lingrad;
ctx.fillRect(-75, -75.150.150);
Copy the code
Before the clip:
After the clip:
In practical development, if you follow this method, you will often make mistakes. The reason is that although the parts outside the path will be hidden, the hidden part when exporting is also part of the image, not just the part inside the clip, so it is still used in practical developmentdrawImage
Methods:drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
During clipping, the starting position of the clipping part on the canvas and the width and height after clipping are obtained, thus realizing the function of clipping.
In actual combat
In the previous part, we summarized the operations of drawing, rotation and clipping. It seems that the overall implementation is quite simple, but it is more complicated in the actual development, because there are many states involved. As mentioned at the beginning of the article, we made a layer of encapsulation implementation based on tui.image-editor
We want this to be a single module as easy to use as possible, UI interaction considerations need to find the best solution, there will be major changes in the location of buttons like menus and overall interaction, based on this: The React Context API is used to address the coupling of layer upon layer component property transfers so that subsequent interactions or style changes do not affect functionality
<ImageEditorContext.Provider
value={{
imageEditor: imageEditorInst,
rootEl: rootEl.current,
imageEditorInfo: imageEditorInfo,
undoStackLength: undoStackLength,
redoStackLength: redoStackLength,
}}
>
{props.imageEditorHeader}
<Controls controls={props.controls} />
<div className={styles.contain}>
<div ref={rootEl} className={styles.tuiImageEditorContain} />
</div>
<Footer controls={props.controls} />
</ImageEditorContext.Provider>
Copy the code
As mentioned at the beginning of the article, we need to achieve good interaction, and the premise of good interaction needs to consider the user’s usage scenarios:
- Pictures to express clearer needs, → such as pasting text, stickers and so on to produce a more vivid or better understanding of the needs of the other side,
- Requirements for content not suitable for outsiders to see, → tailoring
- Picture size does not meet the requirements of the system, such as width, height, size, etc., tailoring requirements → Based on the scene in addition to the cutting function is also intimate in the picture below the real-time display of the basic information of the current picture
Jigsaw processing is also one of the purposes of the development of this component. For jigsaw processing, only one picture can be edited at a time to generate pictures after editing. Canvas loads new pictures for editing. 1. Based on TUI as mentioned at the beginning of this article, in TUI, always fill the canvas with pictures so that there is no room for puzzles on the canvas; 2. If multiple canvases are used, that is, a new canvas will be drawn after loading a new picture, and the operation of each canvas context needs to be switched. Each canvas has its own state, and the interaction becomes difficult to handle. The final result should look something like this:
After processing all images, enter the jigsaw mode, which can be dragged and dragged arbitrarily, and then redraw the page to generate a new canvas object. Enter the single image editing mode and finally export; Of course, the development is still in progress, if you have a better picture editing puzzle solution welcome to come and exchange!