Create an image cropping component with taro starting from 0
Build a rotatable, scalable, draggable high performance clipping component based on Taro. “Like” friends get promoted and pay rise, marry Bai Fumei
github:Github.com/huzhiwu1/im…
If you find this component helpful, welcome start
This article will explain how to make a high performance picture cropping component in the following order
- Parameters that
- Interface layout
- Initialize the
- Image drag-and-drop function
- Zoom in and out of pictures
- Image rotation function
- Canvas canvas drawing
- Export the local address of the drawing picture
1. Parameter Description
To make things easy to start with, all parameters are internal, defined internally by the component, except the aspect ratio of the image and cropping box, which are passed in as external parameters
constructor(props) {
super(props);
this.state = {
imgSrc: props.imgSrc,
cut_ratio: props.cut_ratio, // Trim the width/height ratio of the box
_img_height: 0.// Height of the image _img_width: 0.// Width of the image _img_ratio: 1.// Width/height ratio of the image _img_left: 0.// Images can be used relative to the left margin of the window _img_top: 0.// The image can be used relative to the top margin of the window _window_height: 0.// The height of the available window _window_width: 0.// The window width can be used _canvas_width: 0./ / the width of the canvas _canvas_height: 0./ / the height of the canvas _canvas_left: 0.// Canvas is relative to the left margin of the usable window _canvas_top: 0.// Canvas relative to the top margin of the usable window _cut_width: 200.// Trim the width of the box _cut_height: 200.// Trim the height of the box _cut_left: 0.// Clipping box can be used relative to the left margin of the window _cut_top: 0.// Clipping box can be used relative to the top margin of the window scale: 1.// The default image magnification angle: 0.// Image rotation Angle max_scale: 2.// The maximum magnification of an image min_scale: 0.5.// The smallest number of times the image can be shrunk }; } Copy the code
2. Interface layout
The interface can be roughly divided into three parts:
- Cropped box with grey overlay
- The picture
- canvas
<View className="image-cropper-wrapper">
<View className="bg_container" />// Clipping box with grey overlay <Image/>/ / picture <Canvas/>/ / the canvas</View>
Copy the code
Clipping box and gray overlay will cover the image, and the image needs to respond to touch events, so we need to inbg_container
Add a stylepointer-event:none
So that it does not become the target of mouse events
The clipping frame and gray overlay can be divided into upper, middle and lower parts, and the middle part can be divided into left, middle and right parts:
Trim the box’s 8 white corners, which can be made up of<View>
Absolute positioning to achieve
<View
className="cut_wrapper"
style={{
width: _cut_width+"px",
height: _cut_height+"px", }} > <View className="border border-top-left"></View> <View className="border border-top-right"></View> <View className="border border-right-top"></View> <View className="border border-bottom-right"></View> <View className="border border-right-bottom"></View> <View className="border border-bottom-left"></View> <View className="border border-left-bottom"></View> <View className="border border-left-top"></View> </View> Copy the code
.cut_wrapper {
position: relative;
.border {
background-color: rgba(255, 255, 255, 0.4);
position: absolute;
} .border-top-left { height: 4px; top: -4px; left: -4px; width: 30rpx; } .} Copy the code
3. The initialization
At this stage, we need to initialize the following parameters:
- To obtain
canvas
context - Gets the size of the window available to the device
_window_height
_window_width
- Depending on the size of the available window and the ratio of width to height of the clipped box passed in by the user
_cut_ratio
, calculate the width and height of the clipping frame_cut_height
_cut_width
- Center the clipping box and calculate its relative distance to the usable window
_cut_left
_cut_right
- Get the information of the picture that the user passes in, get the aspect ratio of the picture
_img_ratio
- Fill the cropped box with the long side of the image. Calculate the short side based on the width to height ratio of the image and get the width and height of the image
_img_width
_img_height
- Center the image and calculate its relative distance to the usable window
_img_left
_img_top
async componentWillMount() {
this.initCanvas();
await this.getDeviceInfo();
await this.computedCutSize();
await this.computedCutDistance();
await this.initImageInfo(); await this.computedImageSize(); await this.computedImageDistance(); } Copy the code
4. Drag and drop function of pictures
<Image
className="img"
src={imgSrc}
style={{
top: _img_top+"px", left: _img_left+"px", }} onTouchStart={this._img_touch_start} onTouchMove={this._img_touch_move} onTouchEnd={this._img_touch_end} /> Copy the code
- When touching, record the position of the touch point relative to the picture, so that the picture will move with the finger without bias,
E.touches [0] is the location information of the touch point of the first finger
_img_touch_start(e) {
this._touch_end_flag = false; //_touch_end_flag is the end of touch flag, touchEnd is set to true
if (e.touches.length === 1) {
// One finger touch
// Record the position of the touch point at the beginning
this._img_touch_relative[0] = { // Subtract the position of the image relative to the viewport to get the position of the finger relative to the upper left corner of the image x,y x: e.touches[0].clientX - this.state._img_left, y: e.touches[0].clientY - this.state._img_top, }; } } Copy the code
- Record the location of the movement
_img_touch_move(e) {
// If you end the touch, no more movement
if (this._touch_end_flag) {
console.log(End of "false");
return;
} if (e.touches.length === 1) { // Drag with one finger let left = e.touches[0].clientX - this._img_touch_relative[0].x; let top = e.touches[0].clientY - this._img_touch_relative[0].y; setTimeout((a)= > {// setTimeout is added to trigger setState immediately this.setState({ _img_left: left, _img_top: top, }); }, 0); } } Copy the code
- The end of the movement,
_img_touch_end() {
this._touch_end_flag = true;// Mark the end of the move
}
Copy the code
5. Zoom function of pictures
Zoom action on the mobile end, generallyA narrowing or widening of the distance between the points touched by two fingers, so we need to record the coordinates of two points in each response and calculate the distance between them, and then calculate the change of the distance relative to the last time
<Image
style={{
width: _img_width * scale + "px". height: _img_height * scale + "px". top: _img_top - (_img_height * (scale - 1)) / 2 + "px". left: _img_left - (_img_width * (scale - 1)) / 2 + "px". }} /> Copy the code
In order for the zoom center of the image to be in the center of the image, the left margin and the top margin page need to respond to changes when the image is scaled
_img_height * (scale - 1)// Calculate how much higher the height is
_img_height * (scale - 1) / 2 // Calculate how far the top distance should move, divide by 2 so that both the top and bottom distances move half in order to center
_img_top - (_img_height * (scale - 1)) / 2 + "px".// The distance the top distance moves
Copy the code
- The touch begins and records the distance between two points
_img_touch_start(e){
let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
let height = Math.abs(e.touches[0].clientY - e.touches[1].clientY);
// Calculate the distance between two points
this._hypotenuse_length = Math.sqrt(
Math.pow(width, 2) + Math.pow(height, 2) ); } Copy the code
- Two finger zoom movement, record the distance between two points, and the last distance comparison, calculate the zoom multiple
_img_touch_move(e){
// Double finger zoom
let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
let height = Math.abs(e.touches[0].clientY - e.touches[1].clientY);
let new_hypotenuse_length = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ); // The distance between two points now/the distance between two points last time let newScale =this.state.scale *(new_hypotenuse_length / this._hypotenuse_length); // If the scale is larger than max_scale or min_scale, it does not change, newScale = newScale > this.state.max_scale || newScale < this.state.min_scale ? this.state.scale : newScale; this._hypotenuse_length = new_hypotenuse_length; setTimeout((a)= >{ this.setState({ scale:newScale }) }) } Copy the code
6. Image rotation function
Let’s start with a utility function**Math.atan2(**``**)**
Returns the plane Angle (radian value) between the line segment from the origin (0,0) to (x,y) and the positive X-axis
So that the center of the rotation of the image is in the center of the image, so when I record the touch point,Record the position of the touch point relative to the center of the picture
Position in the center of the image (X,Y)x==_img_left+_img_width/2
y==_img_top+_img_height/2
- Start by touching and record the position of two points relative to the center of the picture
_img_touch_start(e){
// Double finger rotation
this._img_touch_relative = [
{
x:
e.touches[0].clientX - this.state._img_left - this.state._img_width / 2. y: e.touches[0].clientY - this.state._img_top - this.state._img_height / 2. }, { x: e.touches[1].clientX - this.state._img_left - this.state._img_width / 2. y: e.touches[1].clientY - this.state._img_top - this.state._img_height / 2. }, ]; } Copy the code
- Double refers to mobile
_img_touch_move(e){
// Rotate the two fingers and touch the two points relative to the center of the image
let _new_img_touch_relative = [
{
x:
e.touches[0].clientX - this.state._img_left - this.state._img_width / 2. y: e.touches[0].clientY - this.state._img_top - this.state._img_height / 2. }, { x: e.touches[1].clientX - this.state._img_left - this.state._img_width / 2. y: e.touches[1].clientY - this.state._img_top - this.state._img_height / 2. }, ]; // Rotation Angle of the first finger let first_atan_old = (180 / Math.PI) * Math.atan2( this._img_touch_relative[0].y, this._img_touch_relative[0].x ); let first_atan = (180 / Math.PI) * Math.atan2( _new_img_touch_relative[0].y, _new_img_touch_relative[0].x ); let first_deg = first_atan - first_atan_old; // Rotation Angle of the second finger let second_atan_old = (180 / Math.PI) * Math.atan2( this._img_touch_relative[1].y, this._img_touch_relative[1].x ); let second_atan = (180 / Math.PI) * Math.atan2( _new_img_touch_relative[1].y, _new_img_touch_relative[1].x ); let second_deg = second_atan - second_atan_old; // The current rotation Angle let current_deg = 0; if (Math.abs(first_deg) > Math.abs(second_deg)) { current_deg = first_deg; } else { current_deg = second_deg; } // console.log(this._img_touch_relative[1], "img_touch_relative"); this._img_touch_relative = _new_img_touch_relative; setTimeout((a)= > { this.setState( (prevState) = > ({ angle: prevState.angle + current_deg, }), () = > { // console.log(this.state.angle, "angle"); } ); }, 0); } Copy the code
7. Canvas painting
<Canvas
canvasId="my-canvas"
className="my-canvas-class"
disableScroll={false}// Does not respond to scroll axis events on the canvas
style={{ width: _canvas_width+"px", height: _canvas_height+"px", left: _canvas_left+"px", top: _canvas_top+"px", }} ></Canvas> Copy the code
Before drawing a picture, you need to determine the size and position of the canvas. In fact, the size and position of the canvas are consistent with the clipping box
- The position of the image relative to the cropping box so that you know which part of the image to draw
// The user moves and rotates the enlarged image to thu size
let img_width = _img_width * scale;
let img_height = _img_height * scale;
// The relative distance between the image and the clipping box
let distX = _img_left - (_img_width * (scale - 1)) / 2 - _cut_left;
let distY = _img_top - (_img_height * (scale - 1)) / 2 - _cut_top; Copy the code
Once you know the relative distance, move the axis of the canvas
- After the image is rotated, the canvas might rotate,
ctx.rotate()
The center of rotation is zerocanvas
The origin of the coordinate axes, we wantcanvas
The center of the rotation coincides with the center of the image so that the rotating image is perfectly drawn
// Rotate the axis of the canvas according to the rotation Angle of the image,
// To rotate the center of the image, move the canvas's axes first
this.ctx.translate(
distX + img_width / 2. distY + img_height / 2
); this.ctx.rotate((angle * Math.PI) / 180); this.ctx.translate( -distX - img_width / 2. -distY - img_height / 2 ); Copy the code
- Draw pictures
drawImage(imageResource, dx, dy, dWidth, dHeight)
dx | number | The upper-left corner of the imageResource is on the X-axis of the target canvas |
---|---|---|
dy | number | The upper-left corner of the imageResource is positioned on the Y-axis of the target canvas |
dWidth | number | The width of the imageResource drawn on the target canvas, allowing the drawn imageResource to be scaled |
dHeight | number | The height at which the imageResource is drawn on the target canvas, allowing the drawn imageResource to be scaled |
// Draw the image
this.ctx.drawImage(imgSrc, 0.0, img_width, img_height);
this.ctx.draw(false, () = > { console.log("Cloud heart");
callback && callback(); }); Copy the code
The complete code
_draw(callback) {
const {
_cut_height,
_cut_width,
_cut_left,
_cut_top, angle, scale, _img_width, _img_height, _img_left, _img_top, imgSrc, } = this.state; this.setState( { _canvas_height: _cut_height, _canvas_width: _cut_width, _canvas_left: _cut_left, _canvas_top: _cut_top, }, () = > { // The user moves and rotates the enlarged image to thu size let img_width = _img_width * scale; let img_height = _img_height * scale; // The relative distance between the image and the clipping box let distX = _img_left - (_img_width * (scale - 1)) / 2 - _cut_left; let distY = _img_top - (_img_height * (scale - 1)) / 2 - _cut_top; console.log(this.ctx, "Before the CTX"); // Rotate the axis of the canvas according to the rotation Angle of the image, // To rotate the center of the image, move the canvas's axes first this.ctx.translate( distX + img_width / 2. distY + img_height / 2 ); this.ctx.rotate((angle * Math.PI) / 180); this.ctx.translate( -distX - img_width / 2. -distY - img_height / 2 ); console.log(this.ctx, "ctx"); // Move the origin of the canvas according to the relative distance this.ctx.translate(distX, distY); // Draw the image this.ctx.drawImage(imgSrc, 0.0, img_width, img_height); //draw(false) clears the last image and redraws it this.ctx.draw(false, () = > { callback && callback(); }); } ); } Copy the code
8. Export the local address of the image to be drawn
_getImg() {
const { _cut_height, _cut_width, cut_ratio } = this.state;
return new Promise((resolve, reject) = > {
this._draw((a)= > {
Taro.canvasToTempFilePath(
{ width: _cut_width, height: _cut_height, destWidth: 400. destHeight: 400 / cut_ratio, canvasId: "my-canvas". fileType: "png". success(res) { console.log(res, "Success"); resolve(res); }, fail(err) { console.log(err, "err"); reject(err); }, }, this.$scope // If you do not write this, you will get an error. ); }); }); } Copy the code
conclusion
Time: 2020/07/10 If you think the article is good, please give a thumbs-up, thumbs-up are all handsome men and beautiful women, thumbs-up will be promoted and pay rise, ha ha ha, if needed to be reprinted, please indicate the source
This article is formatted using MDNICE