效果图
![在这里插入图片描述](https://img-blog.csdnimg.cn/7ed9fd72462b422d8f40871b6678cd00.png)
import Taro from "@tarojs/taro";
import { View, Canvas, Image } from "@tarojs/components";
import { Component } from 'react'
import "./cut.scss";
function throttle(fn, threshold = 1000 / 40, context = null) {
let _lastExecTime = null;
return function (...args) {
let _nowTime = new Date().getTime();
if (_nowTime - Number(_lastExecTime) > threshold || !_lastExecTime) {
fn.apply(context, args);
_lastExecTime = _nowTime;
}
};
}
export default class ImageCropper extends Component {
static defaultProps = {
imgSrc: "",
cut_ratio: 0.5,
destWidth: null,
img_height: null,
img_width: null,
img_left: null,
img_top: null,
};
_img_touch_relative = [
{
x: 0,
y: 0,
},
{
x: 0,
y: 0,
},
];
_hypotenuse_length = 0;
constructor(props) {
super(props);
this.state = {
imgSrc: props.imgSrc,
cut_ratio: Number(props.cut_ratio),
_img_height: 0,
_img_width: 0,
_img_ratio: 1,
_img_left: 0,
_img_top: 0,
_window_height: 0,
_window_width: 0,
_canvas_width: 0,
_canvas_height: 0,
_canvas_left: 0,
_canvas_top: 0,
_cut_width: 200,
_cut_height: 200,
_cut_left: 0,
_cut_top: 0,
scale: Number(props.scale) || 1,
angle: Number(props.angle) || 0,
quality: 1,
max_scale: 2,
min_scale: 0.5,
};
this._img_touch_move = throttle(this._img_touch_move, 1000 / 40, this);
}
async componentWillMount() {
this.initCanvas();
await this.getDeviceInfo();
await this.computedCutSize();
await this.computedCutDistance();
await this.initImageInfo();
await this.computedImageSize();
await this.computedImageDistance();
}
initCanvas() {
this.ctx = Taro.createCanvasContext("my-canvas", this.$scope);
}
async getDeviceInfo() {
const { windowHeight, windowWidth } = await Taro.getSystemInfoSync();
return new Promise((resolve) => {
this.setState(
{
_window_height: windowHeight,
_window_width: windowWidth,
},
resolve
);
});
}
async initImageInfo() {
const { imgSrc } = this.state;
const { width, height, path } = await Taro.getImageInfo({
src: imgSrc,
});
return new Promise((resolve) => {
this.setState(
{
imgSrc: path,
_img_ratio: width / height,
},
resolve
);
});
}
computedCutSize() {
const { _window_width, _window_height, cut_ratio } = this.state;
let initial_cut_width = Math.floor((_window_width * 2) / 3);
let initial_cut_height = initial_cut_width / cut_ratio;
if (initial_cut_height >= _window_height) {
initial_cut_height = Math.floor(_window_height / 2);
initial_cut_width = initial_cut_height * cut_ratio;
}
return new Promise((resolve) => {
this.setState(
{
_cut_height: initial_cut_height,
_cut_width: initial_cut_width,
},
resolve
);
});
}
computedCutDistance() {
const {
_window_height,
_window_width,
_cut_height,
_cut_width,
} = this.state;
const _cut_top = (_window_height - _cut_height) / 2;
const _cut_left = (_window_width - _cut_width) / 2;
return new Promise((resolve) => {
this.setState(
{
_cut_top,
_cut_left,
},
resolve
);
});
}
computedImageSize() {
const { _img_ratio, _cut_height, _cut_width } = this.state;
let _img_width, _img_height;
if (_img_ratio >= 1) {
_img_width = _cut_width;
_img_height = _img_width / _img_ratio;
} else {
_img_height = _cut_height;
_img_width = _img_height * _img_ratio;
}
return new Promise((resovle) => {
this.setState(
{
_img_height: Number(this.props.img_height) || _img_height,
_img_width: Number(this.props.img_width) || _img_width,
},
resovle
);
});
}
computedImageDistance() {
const {
_img_width,
_img_height,
_window_height,
_window_width,
} = this.state;
let _img_left, _img_top;
_img_left = (_window_width - _img_width) / 2;
_img_top = (_window_height - _img_height) / 2;
return new Promise((resolve) => {
this.setState(
{
_img_left: Number(this.props.img_left) || _img_left,
_img_top: Number(this.props.img_top) || _img_top,
},
resolve
);
});
}
_img_touch_start(e) {
this._touch_end_flag = false;
if (e.touches.length === 1) {
this._touch_pointer_one = true;
this._img_touch_relative[0] = {
x: e.touches[0].clientX - this.state._img_left,
y: e.touches[0].clientY - this.state._img_top,
};
} else {
this._touch_pointer_one = false;
let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
let height = Math.abs(e.touches[0].clientY - e.touches[1].clientY);
this._hypotenuse_length = Math.sqrt(
Math.pow(width, 2) + Math.pow(height, 2)
);
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,
},
];
}
console.log("开始", this._img_touch_relative);
}
_img_touch_move(e) {
if (this._touch_end_flag) {
console.log("结束false");
return;
}
if (e.touches.length === 1 && this._touch_pointer_one) {
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(() => {
this.setState({
_img_left: left,
_img_top: top,
});
}, 0);
} else if (e.touches.length >= 2 && !this._touch_pointer_one) {
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)
);
let newScale =
this.state.scale *
(new_hypotenuse_length / this._hypotenuse_length);
newScale =
newScale > this.state.max_scale ||
newScale < this.state.min_scale
? this.state.scale
: newScale;
this._hypotenuse_length = new_hypotenuse_length;
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,
},
];
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;
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;
let current_deg = 0;
if (first_deg != 0 && first_deg != 360) {
current_deg = first_deg;
} else if (second_deg != 0 && second_deg != 360) {
current_deg = second_deg;
}
this._img_touch_relative = _new_img_touch_relative;
setTimeout(() => {
this.setState(
(prevState) => ({
scale: newScale,
angle: prevState.angle + current_deg,
}),
() => {
}
);
}, 0);
}
}
_img_touch_end() {
this._touch_end_flag = true;
}
_getImg() {
const { _cut_height, _cut_width, cut_ratio, quality } = this.state;
return new Promise((resolve, reject) => {
this._draw(() => {
Taro.canvasToTempFilePath(
{
width: _cut_width,
height: _cut_height,
destWidth: this.props.destWidth || _cut_width,
destHeight: this.props.destWidth
? this.props.destWidth / cut_ratio
: _cut_height,
canvasId: "my-canvas",
fileType: "png",
quality: quality,
success(res) {
console.log(res, "成功");
resolve(res);
},
fail(err) {
console.log(err, "err");
reject(err);
},
},
this.$scope
);
});
});
}
_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,
},
() => {
let img_width = _img_width * scale;
let img_height = _img_height * scale;
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, "ctx前");
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");
this.ctx.translate(distX, distY);
this.ctx.drawImage(imgSrc, 0, 0, img_width, img_height);
this.ctx.draw(false, () => {
console.log("云心");
callback && callback();
});
}
);
}
render() {
const {
_cut_width,
_cut_height,
imgSrc,
_img_height,
_img_width,
_img_left,
_img_top,
scale,
angle,
_canvas_height,
_canvas_width,
_canvas_left,
_canvas_top,
} = this.state;
return (
<View className="wrap">
<View className="image-cropper-wrapper">
<View className="bg_container">
<View className="bg_top"></View>
<View className="bg_middle">
<View className="bg_middle_left"></View>
<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>
<View className="bg_middle_right"></View>
</View>
<View className="bg_bottom"></View>
</View>
<Image
className="img"
src={imgSrc}
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",
transform: `rotate(${angle}deg) `,
}}
onTouchStart={this._img_touch_start.bind(this)}
onTouchMove={this._img_touch_move.bind(this)}
onTouchEnd={this._img_touch_end.bind(this)}
/>
<Canvas
canvasId="my-canvas"
className="my-canvas-class"
disableScroll={false}
style={{
width: _canvas_width + "px",
height: _canvas_height + "px",
left: _canvas_left + "px",
top: _canvas_top + "px",
}}
></Canvas>
</View>
</View>
);
}
}
css 样式
.wrap {
background-color: #fff;
width: 100vw;
height: 100vh;
}
.image-cropper-wrapper {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 1;
background-color: rgba(#1a1a1a, 0.94);
.bg_container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
pointer-events: none;
.bg_top {
height: 25%;
background-color: rgba(0, 0, 0, 0.3);
}
.bg_middle {
display: flex;
.bg_middle_left,
.bg_middle_right {
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
}
.cut_wrapper {
position: relative;
border-radius: 50%;
.border {
background-color: rgba(255, 255, 255, 0.4);
position: absolute;
}
.border-top-left {
height: 4px;
top: -4px;
left: -4px;
width: 30rpx;
}
.border-left-top {
height: 30rpx;
top: -4px;
left: -4px;
width: 4px;
}
.border-top-right {
top: -4px;
right: -4px;
width: 30rpx;
height: 4px;
}
.border-right-top {
top: -4px;
right: -4px;
height: 30rpx;
width: 4px;
}
.border-right-bottom {
bottom: -4px;
right: -4px;
height: 30rpx;
width: 4px;
}
.border-bottom-right {
width: 30rpx;
height: 4px;
bottom: -4px;
right: -4px;
}
.border-left-bottom {
height: 30rpx;
width: 4px;
bottom: -4px;
left: -4px;
}
.border-bottom-left {
width: 30rpx;
height: 4px;
bottom: -4px;
left: -4px;
}
}
}
.bg_bottom {
height: 25%;
background-color: rgba(0, 0, 0, 0.3);
}
}
.img {
position: absolute;
z-index: -1;
backface-visibility: hidden;
transform-origin: center center;
}
.my-canvas-class {
position: absolute;
z-index: -2;
}
}