uni-app图片剪切上传;uview2用使用uview1中的图片剪切上传组件,把原有代码抽取出来
Posted 盘思动
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uni-app图片剪切上传;uview2用使用uview1中的图片剪切上传组件,把原有代码抽取出来相关的知识,希望对你有一定的参考价值。
-
1.其实是个组件页面,移过来就行了。
-
2.原有组件中,可视区域和截图结果区域的长宽是一样的,所以需要调整一下;
-
3.因为剪切图片,是在一个单独页面实现的.所以pages.json中需要加上剪切的页面路径;
-
pages.json
"path": "components/u-avatar-cropper/u-avatar-cropper",
"style":
"navigationBarTitleText": "图片裁剪",
"navigationBarBackgroundColor": "#000000"
组件内2个文件
- weCropper.js
/**
* we-cropper v1.3.9
* (c) 2020 dlhandsome
* @license MIT
*/
(function(global, factory)
typeof exports === \'object\' && typeof module !== \'undefined\' ? module.exports = factory() :
typeof define === \'function\' && define.amd ? define(factory) :
(global.WeCropper = factory());
(this, (function()
\'use strict\';
var device = void 0;
var TOUCH_STATE = [\'touchstarted\', \'touchmoved\', \'touchended\'];
function firstLetterUpper(str)
return str.charAt(0).toUpperCase() + str.slice(1)
function setTouchState(instance)
var arg = [],
len = arguments.length - 1;
while (len-- > 0) arg[len] = arguments[len + 1];
TOUCH_STATE.forEach(function(key, i)
if (arg[i] !== undefined)
instance[key] = arg[i];
);
function validator(instance, o)
Object.defineProperties(instance, o);
function getDevice()
if (!device)
device = uni.getSystemInfoSync();
return device
var tmp = ;
var ref = getDevice();
var pixelRatio = ref.pixelRatio;
var DEFAULT =
id:
default: \'cropper\',
get: function get()
return tmp.id
,
set: function set(value)
if (typeof(value) !== \'string\')
console.error(("id:" + value + " is invalid"));
tmp.id = value;
,
width:
default: 750,
get: function get()
return tmp.width
,
set: function set(value)
if (typeof(value) !== \'number\')
console.error(("width:" + value + " is invalid"));
tmp.width = value;
,
height:
default: 750,
get: function get()
return tmp.height
,
set: function set(value)
if (typeof(value) !== \'number\')
console.error(("height:" + value + " is invalid"));
tmp.height = value;
,
pixelRatio:
default: pixelRatio,
get: function get()
return tmp.pixelRatio
,
set: function set(value)
if (typeof(value) !== \'number\')
console.error(("pixelRatio:" + value + " is invalid"));
tmp.pixelRatio = value;
,
scale:
default: 2.5,
get: function get()
return tmp.scale
,
set: function set(value)
if (typeof(value) !== \'number\')
console.error(("scale:" + value + " is invalid"));
tmp.scale = value;
,
zoom:
default: 5,
get: function get()
return tmp.zoom
,
set: function set(value)
if (typeof(value) !== \'number\')
console.error(("zoom:" + value + " is invalid"));
else if (value < 0 || value > 10)
console.error("zoom should be ranged in 0 ~ 10");
tmp.zoom = value;
,
src:
default: \'\',
get: function get()
return tmp.src
,
set: function set(value)
if (typeof(value) !== \'string\')
console.error(("src:" + value + " is invalid"));
tmp.src = value;
,
cut:
default: ,
get: function get()
return tmp.cut
,
set: function set(value)
if (typeof(value) !== \'object\')
console.error(("cut:" + value + " is invalid"));
tmp.cut = value;
,
boundStyle:
default: ,
get: function get()
return tmp.boundStyle
,
set: function set(value)
if (typeof(value) !== \'object\')
console.error(("boundStyle:" + value + " is invalid"));
tmp.boundStyle = value;
,
onReady:
default: null,
get: function get()
return tmp.ready
,
set: function set(value)
tmp.ready = value;
,
onBeforeImageLoad:
default: null,
get: function get()
return tmp.beforeImageLoad
,
set: function set(value)
tmp.beforeImageLoad = value;
,
onImageLoad:
default: null,
get: function get()
return tmp.imageLoad
,
set: function set(value)
tmp.imageLoad = value;
,
onBeforeDraw:
default: null,
get: function get()
return tmp.beforeDraw
,
set: function set(value)
tmp.beforeDraw = value;
;
var ref$1 = getDevice();
var windowWidth = ref$1.windowWidth;
function prepare()
var self = this;
// v1.4.0 版本中将不再自动绑定we-cropper实例
self.attachPage = function()
var pages = getCurrentPages();
// 获取到当前page上下文
var pageContext = pages[pages.length - 1];
// 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
Object.defineProperty(pageContext, \'wecropper\',
get: function get()
console.warn(
\'Instance will not be automatically bound to the page after v1.4.0\\n\\n\' +
\'Please use a custom instance name instead\\n\\n\' +
\'Example: \\n\' +
\'this.mycropper = new WeCropper(options)\\n\\n\' +
\'// ...\\n\' +
\'this.mycropper.getCropperImage()\'
);
return self
,
configurable: true
);
;
self.createCtx = function()
var id = self.id;
var targetId = self.targetId;
if (id)
self.ctx = self.ctx || uni.createCanvasContext(id);
self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId);
else
console.error("constructor: create canvas context failed, \'id\' must be valuable");
;
self.deviceRadio = windowWidth / 750;
var commonjsGlobal = typeof window !== \'undefined\' ? window : typeof global !== \'undefined\' ? global : typeof self !==
\'undefined\' ? self : ;
function createCommonjsModule(fn, module)
return module =
exports:
, fn(module, module.exports), module.exports;
var tools = createCommonjsModule(function(module, exports)
/**
* String type check
*/
exports.isStr = function(v)
return typeof v === \'string\';
;
/**
* Number type check
*/
exports.isNum = function(v)
return typeof v === \'number\';
;
/**
* Array type check
*/
exports.isArr = Array.isArray;
/**
* undefined type check
*/
exports.isUndef = function(v)
return v === undefined;
;
exports.isTrue = function(v)
return v === true;
;
exports.isFalse = function(v)
return v === false;
;
/**
* Function type check
*/
exports.isFunc = function(v)
return typeof v === \'function\';
;
/**
* Quick object check - this is primarily used to tell
* Objects from primitive values when we know the value
* is a JSON-compliant type.
*/
exports.isObj = exports.isObject = function(obj)
return obj !== null && typeof obj === \'object\'
;
/**
* Strict object type check. Only returns true
* for plain JavaScript objects.
*/
var _toString = Object.prototype.toString;
exports.isPlainObject = function(obj)
return _toString.call(obj) === \'[object Object]\'
;
/**
* Check whether the object has the property.
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
exports.hasOwn = function(obj, key)
return hasOwnProperty.call(obj, key)
;
/**
* Perform no operation.
* Stubbing args to make Flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
*/
exports.noop = function(a, b, c) ;
/**
* Check if val is a valid array index.
*/
exports.isValidArrayIndex = function(val)
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val)
;
);
var tools_7 = tools.isFunc;
var tools_10 = tools.isPlainObject;
var EVENT_TYPE = [\'ready\', \'beforeImageLoad\', \'beforeDraw\', \'imageLoad\'];
function observer()
var self = this;
self.on = function(event, fn)
if (EVENT_TYPE.indexOf(event) > -1)
if (tools_7(fn))
event === \'ready\' ?
fn(self) :
self[("on" + (firstLetterUpper(event)))] = fn;
else
console.error(("event: " + event + " is invalid"));
return self
;
function wxPromise(fn)
return function(obj)
var args = [],
len = arguments.length - 1;
while (len-- > 0) args[len] = arguments[len + 1];
if (obj === void 0) obj = ;
return new Promise(function(resolve, reject)
obj.success = function(res)
resolve(res);
;
obj.fail = function(err)
reject(err);
;
fn.apply(void 0, [obj].concat(args));
)
function draw(ctx, reserve)
if (reserve === void 0) reserve = false;
return new Promise(function(resolve)
ctx.draw(reserve, resolve);
)
var getImageInfo = wxPromise(uni.getImageInfo);
var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath);
var base64 = createCommonjsModule(function(module, exports)
/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
(function(root)
// Detect free variables `exports`.
var freeExports = \'object\' == \'object\' && exports;
// Detect free variable `module`.
var freeModule = \'object\' == \'object\' && module &&
module.exports == freeExports && module;
// Detect free variable `global`, from Node.js or Browserified code, and use
// it as `root`.
var freeGlobal = typeof commonjsGlobal == \'object\' && commonjsGlobal;
if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)
root = freeGlobal;
/*--------------------------------------------------------------------------*/
var InvalidCharacterError = function(message)
this.message = message;
;
InvalidCharacterError.prototype = new Error;
InvalidCharacterError.prototype.name = \'InvalidCharacterError\';
var error = function(message)
// Note: the error messages used throughout this file match those used by
// the native `atob`/`btoa` implementation in Chromium.
throw new InvalidCharacterError(message);
;
var TABLE = \'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\';
// http://whatwg.org/html/common-microsyntaxes.html#space-character
var REGEX_SPACE_CHARACTERS = /[\\t\\n\\f\\r ]/g;
// `decode` is designed to be fully compatible with `atob` as described in the
// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
// The optimized base64-decoding algorithm used is based on @atk’s excellent
// implementation. https://gist.github.com/atk/1020396
var decode = function(input)
input = String(input)
.replace(REGEX_SPACE_CHARACTERS, \'\');
var length = input.length;
if (length % 4 == 0)
input = input.replace(/==?$/, \'\');
length = input.length;
if (
length % 4 == 1 ||
// http://whatwg.org/C#alphanumeric-ascii-characters
/[^+a-zA-Z0-9/]/.test(input)
)
error(
\'Invalid character: the string to be decoded is not correctly encoded.\'
);
var bitCounter = 0;
var bitStorage;
var buffer;
var output = \'\';
var position = -1;
while (++position < length)
buffer = TABLE.indexOf(input.charAt(position));
bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
// Unless this is the first of a group of 4 characters…
if (bitCounter++ % 4)
// …convert the first 8 bits to a single ASCII character.
output += String.fromCharCode(
0xFF & bitStorage >> (-2 * bitCounter & 6)
);
return output;
;
// `encode` is designed to be fully compatible with `btoa` as described in the
// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
var encode = function(input)
input = String(input);
if (/[^\\0-\\xFF]/.test(input))
// Note: no need to special-case astral symbols here, as surrogates are
// matched, and the input is supposed to only contain ASCII anyway.
error(
\'The string to be encoded contains characters outside of the \' +
\'Latin1 range.\'
);
var padding = input.length % 3;
var output = \'\';
var position = -1;
var a;
var b;
var c;
var buffer;
// Make sure any padding is handled outside of the loop.
var length = input.length - padding;
while (++position < length)
// Read three bytes, i.e. 24 bits.
a = input.charCodeAt(position) << 16;
b = input.charCodeAt(++position) << 8;
c = input.charCodeAt(++position);
buffer = a + b + c;
// Turn the 24 bits into four chunks of 6 bits each, and append the
// matching character for each of them to the output.
output += (
TABLE.charAt(buffer >> 18 & 0x3F) +
TABLE.charAt(buffer >> 12 & 0x3F) +
TABLE.charAt(buffer >> 6 & 0x3F) +
TABLE.charAt(buffer & 0x3F)
);
if (padding == 2)
a = input.charCodeAt(position) << 8;
b = input.charCodeAt(++position);
buffer = a + b;
output += (
TABLE.charAt(buffer >> 10) +
TABLE.charAt((buffer >> 4) & 0x3F) +
TABLE.charAt((buffer << 2) & 0x3F) +
\'=\'
);
else if (padding == 1)
buffer = input.charCodeAt(position);
output += (
TABLE.charAt(buffer >> 2) +
TABLE.charAt((buffer << 4) & 0x3F) +
\'==\'
);
return output;
;
var base64 =
\'encode\': encode,
\'decode\': decode,
\'version\': \'0.1.0\'
;
// Some AMD build optimizers, like r.js, check for specific condition patterns
// like the following:
if (
typeof undefined == \'function\' &&
typeof undefined.amd == \'object\' &&
undefined.amd
)
undefined(function()
return base64;
);
else if (freeExports && !freeExports.nodeType)
if (freeModule) // in Node.js or RingoJS v0.8.0+
freeModule.exports = base64;
else // in Narwhal or RingoJS v0.7.0-
for (var key in base64)
base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
else // in Rhino or a web browser
root.base64 = base64;
(commonjsGlobal));
);
function makeURI(strData, type)
return \'data:\' + type + \';base64,\' + strData
function fixType(type)
type = type.toLowerCase().replace(/jpg/i, \'jpeg\');
var r = type.match(/png|jpeg|bmp|gif/)[0];
return \'image/\' + r
function encodeData(data)
var str = \'\';
if (typeof data === \'string\')
str = data;
else
for (var i = 0; i < data.length; i++)
str += String.fromCharCode(data[i]);
return base64.encode(str)
/**
* 获取图像区域隐含的像素数据
* @param canvasId canvas标识
* @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
* @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
* @param width 将要被提取的图像数据矩形区域的宽度
* @param height 将要被提取的图像数据矩形区域的高度
* @param done 完成回调
*/
function getImageData(canvasId, x, y, width, height, done)
uni.canvasGetImageData(
canvasId: canvasId,
x: x,
y: y,
width: width,
height: height,
success: function success(res)
done(res, null);
,
fail: function fail(res)
done(null, res);
);
/**
* 生成bmp格式图片
* 按照规则生成图片响应头和响应体
* @param oData 用来描述 canvas 区域隐含的像素数据 data, width, height = oData
* @returns * base64字符串
*/
function genBitmapImage(oData)
//
// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
//
var biWidth = oData.width;
var biHeight = oData.height;
var biSizeImage = biWidth * biHeight * 3;
var bfSize = biSizeImage + 54; // total header size = 54 bytes
//
// typedef struct tagBITMAPFILEHEADER
// WORD bfType;
// DWORD bfSize;
// WORD bfReserved1;
// WORD bfReserved2;
// DWORD bfOffBits;
// BITMAPFILEHEADER;
//
var BITMAPFILEHEADER = [
// WORD bfType -- The file type signature; must be "BM"
0x42, 0x4D,
// DWORD bfSize -- The size, in bytes, of the bitmap file
bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
// WORD bfReserved1 -- Reserved; must be zero
0, 0,
// WORD bfReserved2 -- Reserved; must be zero
0, 0,
// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
54, 0, 0, 0
];
//
// typedef struct tagBITMAPINFOHEADER
// DWORD biSize;
// LONG biWidth;
// LONG biHeight;
// WORD biPlanes;
// WORD biBitCount;
// DWORD biCompression;
// DWORD biSizeImage;
// LONG biXPelsPerMeter;
// LONG biYPelsPerMeter;
// DWORD biClrUsed;
// DWORD biClrImportant;
// BITMAPINFOHEADER, *PBITMAPINFOHEADER;
//
var BITMAPINFOHEADER = [
// DWORD biSize -- The number of bytes required by the structure
40, 0, 0, 0,
// LONG biWidth -- The width of the bitmap, in pixels
biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
// LONG biHeight -- The height of the bitmap, in pixels
biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
1, 0,
// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
// has a maximum of 2^24 colors (16777216, Truecolor)
24, 0,
// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
0, 0, 0, 0,
// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
// LONG biXPelsPerMeter, unused
0, 0, 0, 0,
// LONG biYPelsPerMeter, unused
0, 0, 0, 0,
// DWORD biClrUsed, the number of color indexes of palette, unused
0, 0, 0, 0,
// DWORD biClrImportant, unused
0, 0, 0, 0
];
var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
var aImgData = oData.data;
var strPixelData = \'\';
var biWidth4 = biWidth << 2;
var y = biHeight;
var fromCharCode = String.fromCharCode;
do
var iOffsetY = biWidth4 * (y - 1);
var strPixelRow = \'\';
for (var x = 0; x < biWidth; x++)
var iOffsetX = x << 2;
strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
fromCharCode(aImgData[iOffsetY + iOffsetX]);
for (var c = 0; c < iPadding; c++)
strPixelRow += String.fromCharCode(0);
strPixelData += strPixelRow;
while (--y)
var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
return strEncoded
/**
* 转换为图片base64
* @param canvasId canvas标识
* @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
* @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
* @param width 将要被提取的图像数据矩形区域的宽度
* @param height 将要被提取的图像数据矩形区域的高度
* @param type 转换图片类型
* @param done 完成回调
*/
function convertToImage(canvasId, x, y, width, height, type, done)
if (done === void 0) done = function() ;
if (type === undefined)
type = \'png\';
type = fixType(type);
if (/bmp/.test(type))
getImageData(canvasId, x, y, width, height, function(data, err)
var strData = genBitmapImage(data);
tools_7(done) && done(makeURI(strData, \'image/\' + type), err);
);
else
console.error(\'暂不支持生成\\\'\' + type + \'\\\'类型的base64图片\');
var CanvasToBase64 =
convertToImage: convertToImage,
// convertToPNG: function (width, height, done)
// return convertToImage(width, height, \'png\', done)
// ,
// convertToJPEG: function (width, height, done)
// return convertToImage(width, height, \'jpeg\', done)
// ,
// convertToGIF: function (width, height, done)
// return convertToImage(width, height, \'gif\', done)
// ,
convertToBMP: function(ref, done)
if (ref === void 0) ref = ;
var canvasId = ref.canvasId;
var x = ref.x;
var y = ref.y;
var width = ref.width;
var height = ref.height;
if (done === void 0) done = function() ;
return convertToImage(canvasId, x, y, width, height, \'bmp\', done)
;
function methods()
var self = this;
var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
var id = self.id;
var targetId = self.targetId;
var pixelRatio = self.pixelRatio;
var ref = self.cut;
var x = ref.x;
if (x === void 0) x = 0;
var y = ref.y;
if (y === void 0) y = 0;
var width = ref.width;
if (width === void 0) width = boundWidth;
var height = ref.height;
if (height === void 0) height = boundHeight;
self.updateCanvas = function(done)
if (self.croperTarget)
// 画布绘制图片
self.ctx.drawImage(
self.croperTarget,
self.imgLeft,
self.imgTop,
self.scaleWidth,
self.scaleHeight
);
tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
self.setBoundStyle(self.boundStyle); // 设置边界样式
self.ctx.draw(false, done);
return self
;
self.pushOrigin = self.pushOrign = function(src)
self.src = src;
tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
return getImageInfo(
src: src
)
.then(function(res)
var innerAspectRadio = res.width / res.height;
var customAspectRadio = width / height;
self.croperTarget = res.path;
if (innerAspectRadio < customAspectRadio)
self.rectX = x;
self.baseWidth = width;
self.baseHeight = width / innerAspectRadio;
self.rectY = y - Math.abs((height - self.baseHeight) / 2);
else
self.rectY = y;
self.baseWidth = height * innerAspectRadio;
self.baseHeight = height;
self.rectX = x - Math.abs((width - self.baseWidth) / 2);
self.imgLeft = self.rectX;
self.imgTop = self.rectY;
self.scaleWidth = self.baseWidth;
self.scaleHeight = self.baseHeight;
self.update();
return new Promise(function(resolve)
self.updateCanvas(resolve);
)
)
.then(function()
tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self);
)
;
self.removeImage = function()
self.src = \'\';
self.croperTarget = \'\';
return draw(self.ctx)
;
self.getCropperBase64 = function(done)
if (done === void 0) done = function() ;
CanvasToBase64.convertToBMP(
canvasId: id,
x: x,
y: y,
width: width,
height: height
, done);
;
self.getCropperImage = function(opt, fn)
var customOptions = opt;
var canvasOptions =
canvasId: id,
x: x,
y: y,
width: width,
height: height
;
var task = function()
return Promise.resolve();
;
if (
tools_10(customOptions) &&
customOptions.original
)
// original mode
task = function()
self.targetCtx.drawImage(
self.croperTarget,
self.imgLeft * pixelRatio,
self.imgTop * pixelRatio,
self.scaleWidth * pixelRatio,
self.scaleHeight * pixelRatio
);
canvasOptions =
canvasId: targetId,
x: x * pixelRatio,
y: y * pixelRatio,
width: width * pixelRatio,
height: height * pixelRatio
;
return draw(self.targetCtx)
;
return task()
.then(function()
if (tools_10(customOptions))
canvasOptions = Object.assign(, canvasOptions, customOptions);
if (tools_7(customOptions))
fn = customOptions;
var arg = canvasOptions.componentContext ?
[canvasOptions, canvasOptions.componentContext] :
[canvasOptions];
return canvasToTempFilePath.apply(null, arg)
)
.then(function(res)
var tempFilePath = res.tempFilePath;
return tools_7(fn) ?
fn.call(self, tempFilePath, null) :
tempFilePath
)
.catch(function(err)
if (tools_7(fn))
fn.call(self, null, err);
else
throw err
)
;
/**
* 获取最新缩放值
* @param oldScale 上一次触摸结束后的缩放值
* @param oldDistance 上一次触摸结束后的双指距离
* @param zoom 缩放系数
* @param touch0 第一指touch对象
* @param touch1 第二指touch对象
* @returns *
*/
var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1)
var xMove, yMove, newDistance;
// 计算二指最新距离
xMove = Math.round(touch1.x - touch0.x);
yMove = Math.round(touch1.y - touch0.y);
newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
return oldScale + 0.001 * zoom * (newDistance - oldDistance)
;
function update()
var self = this;
if (!self.src)
return
self.__oneTouchStart = function(touch)
self.touchX0 = Math.round(touch.x);
self.touchY0 = Math.round(touch.y);
;
self.__oneTouchMove = function(touch)
var xMove, yMove;
// 计算单指移动的距离
if (self.touchended)
return self.updateCanvas()
xMove = Math.round(touch.x - self.touchX0);
yMove = Math.round(touch.y - self.touchY0);
var imgLeft = Math.round(self.rectX + xMove);
var imgTop = Math.round(self.rectY + yMove);
self.outsideBound(imgLeft, imgTop);
self.updateCanvas();
;
self.__twoTouchStart = function(touch0, touch1)
var xMove, yMove, oldDistance;
self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
// 计算两指距离
xMove = Math.round(touch1.x - touch0.x);
yMove = Math.round(touch1.y - touch0.y);
oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
self.oldDistance = oldDistance;
;
self.__twoTouchMove = function(touch0, touch1)
var oldScale = self.oldScale;
var oldDistance = self.oldDistance;
var scale = self.scale;
var zoom = self.zoom;
self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
// 设定缩放范围
self.newScale <= 1 && (self.newScale = 1);
self.newScale >= scale && (self.newScale = scale);
self.scaleWidth = Math.round(self.newScale * self.baseWidth);
self.scaleHeight = Math.round(self.newScale * self.baseHeight);
var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
self.outsideBound(imgLeft, imgTop);
self.updateCanvas();
;
self.__xtouchEnd = function()
self.oldScale = self.newScale;
self.rectX = self.imgLeft;
self.rectY = self.imgTop;
;
var handle =
// 图片手势初始监测
touchStart: function touchStart(e)
var self = this;
var ref = e.touches;
var touch0 = ref[0];
var touch1 = ref[1];
if (!self.src)
return
setTouchState(self, true, null, null);
// 计算第一个触摸点的位置,并参照改点进行缩放
self.__oneTouchStart(touch0);
// 两指手势触发
if (e.touches.length >= 2)
self.__twoTouchStart(touch0, touch1);
,
// 图片手势动态缩放
touchMove: function touchMove(e)
var self = this;
var ref = e.touches;
var touch0 = ref[0];
var touch1 = ref[1];
if (!self.src)
return
setTouchState(self, null, true);
// 单指手势时触发
if (e.touches.length === 1)
self.__oneTouchMove(touch0);
// 两指手势触发
if (e.touches.length >= 2)
self.__twoTouchMove(touch0, touch1);
,
touchEnd: function touchEnd(e)
var self = this;
if (!self.src)
return
setTouchState(self, false, false, true);
self.__xtouchEnd();
;
function cut()
var self = this;
var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
var boundHeight = self.height;
// 裁剪框默认高度,即整个画布高度
var ref = self.cut;
var x = ref.x;
if (x === void 0) x = 0;
var y = ref.y;
if (y === void 0) y = 0;
var width = ref.width;
if (width === void 0) width = boundWidth;
var height = ref.height;
if (height === void 0) height = boundHeight;
/**
* 设置边界
* @param imgLeft 图片左上角横坐标值
* @param imgTop 图片左上角纵坐标值
*/
self.outsideBound = function(imgLeft, imgTop)
self.imgLeft = imgLeft >= x ?
x :
self.scaleWidth + imgLeft - x <= width ?
x + width - self.scaleWidth :
imgLeft;
self.imgTop = imgTop >= y ?
y :
self.scaleHeight + imgTop - y <= height ?
y + height - self.scaleHeight :
imgTop;
;
/**
* 设置边界样式
* @param color 边界颜色
*/
self.setBoundStyle = function(ref)
if (ref === void 0) ref = ;
var color = ref.color;
if (color === void 0) color = \'#04b00f\';
var mask = ref.mask;
if (mask === void 0) mask = \'rgba(0, 0, 0, 0.3)\';
var lineWidth = ref.lineWidth;
if (lineWidth === void 0) lineWidth = 1;
var half = lineWidth / 2;
var boundOption = [
start:
x: x - half,
y: y + 10 - half
,
step1:
x: x - half,
y: y - half
,
step2:
x: x + 10 - half,
y: y - half
,
start:
x: x - half,
y: y + height - 10 + half
,
step1:
x: x - half,
y: y + height + half
,
step2:
x: x + 10 - half,
y: y + height + half
,
start:
x: x + width - 10 + half,
y: y - half
,
step1:
x: x + width + half,
y: y - half
,
step2:
x: x + width + half,
y: y + 10 - half
,
start:
x: x + width + half,
y: y + height - 10 + half
,
step1:
x: x + width + half,
y: y + height + half
,
step2:
x: x + width - 10 + half,
y: y + height + half
];
// 绘制半透明层
self.ctx.beginPath();
self.ctx.setFillStyle(mask);
self.ctx.fillRect(0, 0, x, boundHeight);
self.ctx.fillRect(x, 0, width, y);
self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
self.ctx.fill();
boundOption.forEach(function(op)
self.ctx.beginPath();
self.ctx.setStrokeStyle(color);
self.ctx.setLineWidth(lineWidth);
self.ctx.moveTo(op.start.x, op.start.y);
self.ctx.lineTo(op.step1.x, op.step1.y);
self.ctx.lineTo(op.step2.x, op.step2.y);
self.ctx.stroke();
);
;
var version = "1.3.9";
var WeCropper = function WeCropper(params)
var self = this;
var _default = ;
validator(self, DEFAULT);
Object.keys(DEFAULT).forEach(function(key)
_default[key] = DEFAULT[key].default;
);
Object.assign(self, _default, params);
self.prepare();
self.attachPage();
self.createCtx();
self.observer();
self.cutt();
self.methods();
self.init();
self.update();
return self
;
WeCropper.prototype.init = function init()
var self = this;
var src = self.src;
self.version = version;
typeof self.onReady === \'function\' && self.onReady(self.ctx, self);
if (src)
self.pushOrign(src);
else
self.updateCanvas();
setTouchState(self, false, false, false);
self.oldScale = 1;
self.newScale = 1;
return self
;
Object.assign(WeCropper.prototype, handle);
WeCropper.prototype.prepare = prepare;
WeCropper.prototype.observer = observer;
WeCropper.prototype.methods = methods;
WeCropper.prototype.cutt = cut;
WeCropper.prototype.update = update;
return WeCropper;
)));
- u-avatar-cropper.vue
<template>
<view class="content">
<view class="cropper-wrapper" :>
<canvas
class="cropper"
:disable-scroll="true"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:
canvas-id="cropper"
id="cropper"
></canvas>
<canvas
class="cropper"
:disable-scroll="true"
:
canvas-id="targetId"
id="targetId"
></canvas>
</view>
<view class="cropper-buttons safe-area-padding" :>
<!-- #ifdef H5 -->
<view class="upload" @tap="uploadTap">选择图片</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="upload" @tap="uploadTap">重新选择</view>
<!-- #endif -->
<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
</view>
</view>
</template>
<script>
import WeCropper from \'./weCropper.js\';
export default
props:
// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
boundStyle:
type: Object,
default()
return
lineWidth: 4,
borderColor: \'rgb(245, 245, 245)\',
mask: \'rgba(0, 0, 0, 0.35)\'
;
,
// // 裁剪框宽度,单位rpx
// rectWidth:
// type: [String, Number],
// default: 400
// ,
// // 裁剪框高度,单位rpx
// rectHeight:
// type: [String, Number],
// default: 400
// ,
// // 输出图片宽度,单位rpx
// destWidth:
// type: [String, Number],
// default: 400
// ,
// // 输出图片高度,单位rpx
// destHeight:
// type: [String, Number],
// default: 400
// ,
// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
// fileType:
// type: String,
// default: \'jpg\',
// ,
// // 生成的图片质量
// // H5上无效,目前不考虑使用此参数
// quality:
// type: [Number, String],
// default: 1
//
,
data()
return
// 底部导航的高度
bottomNavHeight: 50,
originWidth: 200,
width: 0,
height: 0,
cropperOpt:
id: \'cropper\',
targetId: \'targetCropper\',
pixelRatio: 1,
width: 0,
height: 0,
scale: 2.5,
zoom: 8,
cut:
x: (this.width - this.originWidth) / 2,
y: (this.height - this.originWidth) / 2,
width: this.originWidth,
height: this.originWidth
,
boundStyle:
lineWidth: uni.upx2px(this.boundStyle.lineWidth),
mask: this.boundStyle.mask,
color: this.boundStyle.borderColor
,
// 裁剪框和输出图片的尺寸,高度默认等于宽度
// 输出图片宽度,单位px
// destWidth: 200,
// // 裁剪框宽度,单位px
// rectWidth: 200,
// // 输出的图片类型,如果\'png\'类型发现裁剪的图片太大,改成"jpg"即可
// fileType: \'jpg\',
src: \'\', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
;
,
onLoad(option)
let rectInfo = uni.getSystemInfoSync();
this.width = rectInfo.windowWidth;
this.height = rectInfo.windowHeight - this.bottomNavHeight;
this.cropperOpt.width = this.width;
this.cropperOpt.height = this.height;
this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
if (option.destWidth) this.destWidth = option.destWidth;
if (option.destHeight) this.destHeight = option.destHeight;
if (option.rectWidth)
let rectWidth = Number(option.rectWidth);
let rectHeight = Number(option.rectHeight);
this.cropperOpt.cut =
x: (this.width - rectWidth) / 2,
y: (this.height - rectHeight) / 2,
width: rectWidth,
height: rectHeight
;
this.rectWidth = option.rectWidth;
this.rectHeight = option.rectHeight;
if (option.fileType) this.fileType = option.fileType;
// 初始化
this.cropper = new WeCropper(this.cropperOpt)
.on(\'ready\', ctx =>
// wecropper is ready for work!
)
.on(\'beforeImageLoad\', ctx =>
// before picture loaded, i can do something
)
.on(\'imageLoad\', ctx =>
// picture loaded
)
.on(\'beforeDraw\', (ctx, instance) =>
// before canvas draw,i can do something
);
// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
uni.setNavigationBarColor(
frontColor: \'#ffffff\',
backgroundColor: \'#000000\'
);
uni.chooseImage(
count: 1, // 默认9
sizeType: [\'compressed\'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: [\'album\', \'camera\'], // 可以指定来源是相册还是相机,默认二者都有
success: res =>
this.src = res.tempFilePaths[0];
// 获取裁剪图片资源后,给data添加src属性及其值
this.cropper.pushOrign(this.src);
);
,
methods:
touchStart(e)
this.cropper.touchStart(e);
,
touchMove(e)
this.cropper.touchMove(e);
,
touchEnd(e)
this.cropper.touchEnd(e);
,
getCropperImage(isPre = false)
if(!this.src) return this.$u.toast(\'请先选择图片再裁剪\');
let cropper_opt =
destHeight: Number(this.destHeight), // uni.canvasToTempFilePath要求这些参数为数值
destWidth: Number(this.destWidth),
fileType: this.fileType
;
this.cropper.getCropperImage(cropper_opt, (path, err) =>
if (err)
uni.showModal(
title: \'温馨提示\',
content: err.message
);
else
if (isPre)
uni.previewImage(
current: \'\', // 当前显示图片的 http 链接
urls: [path] // 需要预览的图片 http 链接列表
);
else
uni.$emit(\'uAvatarCropper\', path);
this.$u.route(
type: \'back\'
);
);
,
uploadTap()
const self = this;
uni.chooseImage(
count: 1, // 默认9
sizeType: [\'original\', \'compressed\'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: [\'album\', \'camera\'], // 可以指定来源是相册还是相机,默认二者都有
success: (res) =>
self.src = res.tempFilePaths[0];
// 获取裁剪图片资源后,给data添加src属性及其值
self.cropper.pushOrign(this.src);
);
;
</script>
<style scoped lang="scss">
.content
background: rgba(255, 255, 255, 1);
.cropper
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11;
.cropper-buttons
background-color: #000000;
color: #eee;
.cropper-wrapper
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #000;
.cropper-buttons
width: 100vw;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
font-size: 28rpx;
.cropper-buttons .upload,
.cropper-buttons .getCropperImage
width: 50%;
text-align: center;
.cropper-buttons .upload
text-align: left;
padding-left: 50rpx;
.cropper-buttons .getCropperImage
text-align: right;
padding-right: 50rpx;
</style>
- 跳转剪切组件的页面,以及剪切完,回收处理返回图片上传服务器 test.vue
<template>
<view class="wrap">
<view class="u-avatar-wrap">
<image class="u-avatar-demo" :src="avatar" mode="aspectFill"></image>
</view>
<u-button @tap="chooseAvatar">选择图片</u-button>
<u-button @tap="saveAvatar" class="save_btn">保存头像</u-button>
</view>
</template>
<script>
export default
data ()
return
avatar:\'\'
,
onLoad(options)
this.avatar = options.avatar
,
created()
// 监听从裁剪页发布的事件,获得裁剪结果
uni.$on(\'uAvatarCropper\', path =>
this.avatar = path;
// 可以在此上传到服务端
uni.uploadFile(
url: \'http://*****.com/api.php/Upload/uploadMoreImg\',
filePath: path,
name: \'file\',
complete: (res) =>
this.avatar = JSON.parse(res.data).data;
console.log(\'-----\');
console.log(res);
console.log(JSON.parse(res.data));
console.log(JSON.parse(res.data).data);
);
)
,
methods:
chooseAvatar()
// 此为uView的跳转方法,详见"文档-JS"部分,也可以用uni的uni.navigateTo
this.$u.route(
// 关于此路径,请见下方"注意事项"
url: \'/components/u-avatar-cropper/u-avatar-cropper\',
// 内部已设置以下默认参数值,可不传这些参数
params:
// 输出图片宽度,高等于宽,单位px
destWidth: 300,
destHeight: 600,
// 裁剪框宽度,高等于宽,单位px
rectWidth: 200,
rectHeight: 400,
// 输出的图片类型,如果\'png\'类型发现裁剪的图片太大,改成"jpg"即可
fileType: \'jpg\',
)
,
saveAvatar()
this.$request(\'User/save_avatar\',avatar:this.avatar).then((data)=>
uni.navigateBack(
)
)
</script>
<style lang="scss" scoped>
.wrap
padding: 40rpx;
.u-avatar-wrap
margin-top: 80rpx;
overflow: hidden;
margin-bottom: 80rpx;
text-align: center;
.u-avatar-demo
width: 150rpx;
height: 150rpx;
border-radius: 100rpx;
.save_btn
margin-top: 60rpx;
@include size(650rpx,100rpx);
background-color: #F76E64;
border-radius: 50rpx;
color: #FFFFFF;
@include flexCenter();
</style>
相信坚持的力量,日复一日的习惯.
在uni-app中引入和使用uViewUI
参考技术A 先来看遇到如下图的错误uViewUI在HBuilder X的插件市场: https://ext.dcloud.net.cn/plugin?id=1593
uViewUI官方地址: https://uviewui.com/
1.1、安装
1.2、更新
已经安装想更新版本可以使用
1.3、查看版本号
有两种方式可以查询到正在使用的uView的版本:
通过console.log打印的形式
// 或者(二者等价)
console.log(this.$u.config.version);
uView依赖SCSS,您必须要安装此插件,否则无法正常运行。
2.1、HBuilderX工具中要安装scss插件: HX菜单的 工具->插件安装中找到"scss/sass编译"插件进行安装
在插件市场直接导入: https://ext.dcloud.net.cn/plugin?id=2046
注:插件市场不支持IE跳转,建议用谷歌浏览器
如果您的项目是由vue-cli创造的,请通过以下命令安装对sass(scss)的支持,如果已安装,请略过。
3、对相关文件进行配置
3.1、在main.js 文件全局引入uView
3.2、uni.scss 文件 引入uView的全局SCSS主题文件
3.3、在App.vue文件引入uView基础样式
4、全局引入和按需加载
4.1、全局引入
在pages.json配置easycom
注:uni-app为了调试性能的原因,修改easycom规则不会实时生效,配置完后,您需要重启HX或重新编译项目才能正常使用uView的功能。
请确保您的pages.json中只有一个easycom分区,否则请自行合并多个约会规则。
// pages.json "easycom": "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" , // 此为本身已有的内容 "pages": [ // ...... ]
4.2、按需加载
某些情况下,您可能不想通过easycom引用组件(虽然我们极力推荐您使用easycom),那么您可以使用import这个常规的约会方式,如下:
以上是关于uni-app图片剪切上传;uview2用使用uview1中的图片剪切上传组件,把原有代码抽取出来的主要内容,如果未能解决你的问题,请参考以下文章
Android中使用OKHttp上传图片,从相机和相册中获取图片并剪切