|
- <template>
- <view
- class="u-canvas"
- :id="rootId"
- :style="{
- width: useRootHeightAndWidth ? '100%' : actualWidth + unit,
- height: useRootHeightAndWidth ? '100%' : actualHeight + unit
- }"
- >
- <!-- #ifdef MP || H5 -->
- <canvas
- class="u-canvas__canvas"
- :id="canvasId"
- :canvas-id="canvasId"
- type="2d"
- :disable-scroll="disableScroll"
- :style="{ width: actualWidth + unit, height: actualHeight + unit }"
- @touchstart="onTouchStart"
- @touchmove="onTouchMove"
- @touchend="onTouchEnd"
- />
- <!-- #endif -->
-
- <!-- #ifdef APP-PLUS -->
- <canvas
- class="u-canvas__canvas"
- :id="canvasId"
- :canvas-id="canvasId"
- :disable-scroll="disableScroll"
- :style="{ width: actualWidth + unit, height: actualHeight + unit }"
- @touchstart="onTouchStart"
- @touchmove="onTouchMove"
- @touchend="onTouchEnd"
- />
- <!-- #endif -->
-
- <!-- #ifdef APP-NVUE -->
- <gcanvas
- class="u-canvas__canvas"
- ref="gcanvas"
- :style="{ width: actualWidth + unit, height: actualHeight + unit }"
- @touchstart="onTouchStart"
- @touchmove="onTouchMove"
- @touchend="onTouchEnd"
- />
- <!-- #endif -->
- </view>
- </template>
-
- <script>
- // #ifdef APP-NVUE
- import {
- enable,
- WeexBridge,
- Image as GImage
- } from '../../libs/util/gcanvas/index.js';
- // #endif
-
- export default {
- name: 'u-canvas',
- emits: ['ready', 'touchstart', 'touchmove', 'touchend'],
- props: {
- canvasId: {
- type: String,
- default: () => `u-canvas${Math.floor(Math.random() * 1000000)}`
- },
- width: {
- type: [String, Number],
- default: 300
- },
- height: {
- type: [String, Number],
- default: 300
- },
- unit: {
- type: String,
- default: 'px'
- },
- useRootHeightAndWidth: {
- type: Boolean,
- default: false
- },
- bgColor: {
- type: String,
- default: '#ffffff'
- },
- disableScroll: {
- type: Boolean,
- default: false
- }
- },
- data() {
- return {
- rootId: `u-canvas-root-${Math.floor(Math.random() * 1000000)}`,
- ctx: null,
- widthLocal: this.parseSize(this.width),
- heightLocal: this.parseSize(this.height),
- fontSize: 12,
- fontFamily: 'sans-serif',
- fontWeight: 'normal',
- dpr: 1
- };
- },
- computed: {
- actualWidth() {
- return this.useRootHeightAndWidth ? this.widthLocal : this.parseSize(this.width);
- },
- actualHeight() {
- return this.useRootHeightAndWidth ? this.heightLocal : this.parseSize(this.height);
- }
- },
- watch: {
- width() {
- this.refresh();
- },
- height() {
- this.refresh();
- },
- bgColor() {
- this.clearCanvas();
- }
- },
- created() {
- this._canvasNode = null;
- this._selectorResult = null;
- this._canvasElement = null;
- this._imageCache = Object.create(null);
- this._isNvue = false;
- },
- mounted() {
- this.$nextTick(() => {
- this.initCanvas();
- });
- },
- methods: {
- parseSize(value) {
- if (typeof value === 'number') {
- return value;
- }
- if (typeof value !== 'string') {
- return 0;
- }
- if (value.endsWith('rpx') || value.endsWith('upx')) {
- return uni.upx2px(parseFloat(value));
- }
- if (value.endsWith('px')) {
- return parseFloat(value) || 0;
- }
- return parseFloat(value) || 0;
- },
- onTouchStart(event) {
- this.$emit('touchstart', event);
- },
- onTouchMove(event) {
- this.$emit('touchmove', event);
- },
- onTouchEnd(event) {
- this.$emit('touchend', event);
- },
- async getCanvasNode(id = this.canvasId, isCanvas = true) {
- return new Promise((resolve) => {
- try {
- // #ifdef APP-NVUE
- setTimeout(() => {
- const gcanvas = this.$refs.gcanvas;
- if (!gcanvas) {
- resolve(false);
- return;
- }
- this._isNvue = true;
- resolve(enable(gcanvas, { bridge: WeexBridge }));
- }, 100);
- // #endif
-
- // #ifndef APP-NVUE
- uni.createSelectorQuery()
- .in(this)
- .select(`#${id}`)
- .fields(
- {
- node: isCanvas,
- size: true
- },
- (res) => {
- resolve(res || false);
- }
- )
- .exec();
- // #endif
- } catch (error) {
- console.error('获取画布节点失败:', error);
- resolve(false);
- }
- });
- },
- getCanvasElement() {
- return this._canvasElement || null;
- },
- getRawContext() {
- return this.ctx;
- },
- getCanvasContext() {
- // #ifdef APP-PLUS
- return uni.createCanvasContext(this.canvasId, this);
- // #endif
-
- // #ifdef APP-NVUE
- return this._canvasElement && typeof this._canvasElement.getContext === 'function'
- ? this._canvasElement.getContext('2d')
- : null;
- // #endif
-
- // #ifdef MP || H5
- return this._canvasElement && typeof this._canvasElement.getContext === 'function'
- ? this._canvasElement.getContext('2d')
- : null;
- // #endif
- },
- async setNewSize() {
- const rootNode = await this.getCanvasNode(this.rootId, false);
- if (!rootNode) {
- return;
- }
- if (rootNode.width) {
- this.widthLocal = rootNode.width;
- }
- if (rootNode.height) {
- this.heightLocal = rootNode.height;
- }
- },
- async initCanvas(force = false) {
- try {
- if (this.useRootHeightAndWidth) {
- await this.setNewSize();
- }
- if (this.ctx && !force) {
- this.$emit('ready', {
- width: this.actualWidth,
- height: this.actualHeight
- });
- return true;
- }
-
- this._canvasNode = await this.getCanvasNode(this.canvasId);
- if (!this._canvasNode) {
- return false;
- }
-
- this._selectorResult = this._canvasNode;
- this._canvasElement = this._canvasNode.node || this._canvasNode;
- this.dpr = uni.getSystemInfoSync().pixelRatio || 1;
-
- // #ifdef MP || H5
- if (this._canvasElement) {
- this._canvasElement.width = Math.ceil(this.actualWidth * this.dpr);
- this._canvasElement.height = Math.ceil(this.actualHeight * this.dpr);
- }
- // #endif
-
- this.ctx = this.getCanvasContext();
- if (!this.ctx) {
- return false;
- }
-
- // #ifdef MP || H5
- if (typeof this.ctx.setTransform === 'function') {
- this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
- } else if (typeof this.ctx.scale === 'function') {
- this.ctx.scale(this.dpr, this.dpr);
- }
- // #endif
-
- this.applyFont();
- this.clearCanvas();
- this.$emit('ready', {
- width: this.actualWidth,
- height: this.actualHeight
- });
- return true;
- } catch (error) {
- console.error('初始化Canvas失败:', error);
- return false;
- }
- },
- refresh() {
- return this.initCanvas(true);
- },
- getWidth() {
- return this.actualWidth;
- },
- getHeight() {
- return this.actualHeight;
- },
- clearCanvas() {
- if (!this.ctx) return;
- this.clearRect(0, 0, this.actualWidth, this.actualHeight);
- if (this.bgColor && this.bgColor !== 'transparent') {
- this.beginPath();
- this.rect(0, 0, this.actualWidth, this.actualHeight);
- this.setFillStyle(this.bgColor);
- this.fill();
- }
- this.draw();
- },
- callContext(method, ...args) {
- if (this.ctx && typeof this.ctx[method] === 'function') {
- return this.ctx[method](...args);
- }
- return undefined;
- },
- rect(x, y, width, height) {
- return this.callContext('rect', x, y, width, height);
- },
- clearRect(x, y, width, height) {
- return this.callContext('clearRect', x, y, width, height);
- },
- fillRect(x, y, width, height) {
- return this.callContext('fillRect', x, y, width, height);
- },
- strokeRect(x, y, width, height) {
- return this.callContext('strokeRect', x, y, width, height);
- },
- fill() {
- return this.callContext('fill');
- },
- stroke() {
- return this.callContext('stroke');
- },
- beginPath() {
- return this.callContext('beginPath');
- },
- closePath() {
- return this.callContext('closePath');
- },
- moveTo(x, y) {
- return this.callContext('moveTo', x, y);
- },
- lineTo(x, y) {
- return this.callContext('lineTo', x, y);
- },
- arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
- return this.callContext('arc', x, y, radius, startAngle, endAngle, anticlockwise);
- },
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
- return this.callContext('bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y);
- },
- quadraticCurveTo(cpx, cpy, x, y) {
- return this.callContext('quadraticCurveTo', cpx, cpy, x, y);
- },
- clip() {
- return this.callContext('clip');
- },
- save() {
- return this.callContext('save');
- },
- restore() {
- return this.callContext('restore');
- },
- translate(x, y) {
- return this.callContext('translate', x, y);
- },
- rotate(angle) {
- return this.callContext('rotate', angle);
- },
- scale(x, y) {
- return this.callContext('scale', x, y);
- },
- setFillStyle(color) {
- if (!this.ctx) return;
- if (typeof this.ctx.setFillStyle === 'function') {
- this.ctx.setFillStyle(color);
- } else {
- this.ctx.fillStyle = color;
- }
- },
- setStrokeStyle(color) {
- if (!this.ctx) return;
- if (typeof this.ctx.setStrokeStyle === 'function') {
- this.ctx.setStrokeStyle(color);
- } else {
- this.ctx.strokeStyle = color;
- }
- },
- setLineWidth(width) {
- if (!this.ctx) return;
- if (typeof this.ctx.setLineWidth === 'function') {
- this.ctx.setLineWidth(width);
- } else {
- this.ctx.lineWidth = width;
- }
- },
- setLineCap(lineCap = 'round') {
- if (!this.ctx) return;
- if (typeof this.ctx.setLineCap === 'function') {
- this.ctx.setLineCap(lineCap);
- } else {
- this.ctx.lineCap = lineCap;
- }
- },
- setLineJoin(lineJoin = 'round') {
- if (!this.ctx) return;
- if (typeof this.ctx.setLineJoin === 'function') {
- this.ctx.setLineJoin(lineJoin);
- } else {
- this.ctx.lineJoin = lineJoin;
- }
- },
- setTextAlign(align = 'left') {
- if (!this.ctx) return;
- if (typeof this.ctx.setTextAlign === 'function') {
- this.ctx.setTextAlign(align);
- } else {
- this.ctx.textAlign = align;
- }
- },
- setTextBaseline(baseline = 'alphabetic') {
- if (!this.ctx) return;
- if (typeof this.ctx.setTextBaseline === 'function') {
- this.ctx.setTextBaseline(baseline);
- } else {
- this.ctx.textBaseline = baseline;
- }
- },
- setFontSize(fontSize) {
- this.fontSize = Number(fontSize) || this.fontSize;
- if (this.ctx && typeof this.ctx.setFontSize === 'function') {
- this.ctx.setFontSize(this.fontSize);
- }
- this.applyFont();
- },
- setFont(font) {
- if (!this.ctx) return;
- if ('font' in this.ctx) {
- this.ctx.font = font;
- return;
- }
- const matched = String(font).match(/(\d+(?:\.\d+)?)px/);
- if (matched) {
- this.fontSize = Number(matched[1]);
- }
- this.applyFont();
- },
- setGlobalAlpha(alpha) {
- if (!this.ctx) return;
- if (typeof this.ctx.setGlobalAlpha === 'function') {
- this.ctx.setGlobalAlpha(alpha);
- } else {
- this.ctx.globalAlpha = alpha;
- }
- },
- setShadow(offsetX = 0, offsetY = 0, blur = 0, color = 'rgba(0,0,0,0)') {
- if (!this.ctx) return;
- if (typeof this.ctx.setShadow === 'function') {
- this.ctx.setShadow(offsetX, offsetY, blur, color);
- return;
- }
- this.ctx.shadowOffsetX = offsetX;
- this.ctx.shadowOffsetY = offsetY;
- this.ctx.shadowBlur = blur;
- this.ctx.shadowColor = color;
- },
- setLineStyle(lineColor, lineWidth) {
- this.setLineCap('round');
- this.setLineJoin('round');
- this.setStrokeStyle(lineColor);
- this.setLineWidth(lineWidth);
- },
- applyFont() {
- if (!this.ctx) return;
- const font = `${this.fontWeight === 'normal' ? '' : `${this.fontWeight} `}${this.fontSize}px ${this.fontFamily}`.trim();
- if ('font' in this.ctx) {
- this.ctx.font = font;
- } else if (typeof this.ctx.setFont === 'function') {
- this.ctx.setFont(font);
- } else if (typeof this.ctx.setFontSize === 'function') {
- this.ctx.setFontSize(this.fontSize);
- }
- },
- fillText(text, x, y) {
- return this.callContext('fillText', String(text), x, y);
- },
- measureText(text) {
- if (this.ctx && typeof this.ctx.measureText === 'function') {
- return this.ctx.measureText(String(text));
- }
- return {
- width: String(text).length * this.fontSize * 0.6
- };
- },
- createLinearGradient(x0, y0, x1, y1) {
- if (this.ctx && typeof this.ctx.createLinearGradient === 'function') {
- return this.ctx.createLinearGradient(x0, y0, x1, y1);
- }
- return null;
- },
- createRadialGradient(x0, y0, r0, x1, y1, r1) {
- if (this.ctx && typeof this.ctx.createRadialGradient === 'function') {
- return this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
- }
- return null;
- },
- loadImage(src) {
- if (this._imageCache[src]) {
- return Promise.resolve(this._imageCache[src]);
- }
- return new Promise((resolve, reject) => {
- let image = null;
-
- // #ifdef APP-NVUE
- image = new GImage();
- // #endif
-
- // #ifdef MP
- const canvas = this.getCanvasElement();
- if (canvas && typeof canvas.createImage === 'function') {
- image = canvas.createImage();
- }
- // #endif
-
- // #ifdef H5
- image = new Image();
- image.crossOrigin = 'anonymous';
- // #endif
-
- if (!image) {
- resolve(src);
- return;
- }
-
- image.onload = () => {
- this._imageCache[src] = image;
- resolve(image);
- };
- image.onerror = reject;
- image.src = src;
- });
- },
- async drawImage(source, ...args) {
- if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
- return false;
- }
- if (typeof source !== 'string' || (typeof this.ctx.setFillStyle === 'function' && !this._isNvue)) {
- this.ctx.drawImage(source, ...args);
- return true;
- }
- const image = await this.loadImage(source);
- this.ctx.drawImage(image, ...args);
- return true;
- },
- draw(isLastDraw = false, callback) {
- if (this.ctx && typeof this.ctx.draw === 'function') {
- return this.ctx.draw(isLastDraw, callback);
- }
- if (typeof callback === 'function') {
- setTimeout(callback, 0);
- }
- return undefined;
- },
- toTempFilePath(options = {}) {
- return new Promise((resolve, reject) => {
- const width = options.width || this.actualWidth;
- const height = options.height || this.actualHeight;
- const request = {
- x: options.x || 0,
- y: options.y || 0,
- width,
- height,
- destWidth: options.destWidth || width,
- destHeight: options.destHeight || height,
- fileType: options.fileType || 'png',
- quality: options.quality === undefined ? 1 : options.quality
- };
- const success = (res) => {
- if (typeof options.success === 'function') options.success(res);
- resolve(res);
- };
- const fail = (err) => {
- if (typeof options.fail === 'function') options.fail(err);
- reject(err);
- };
- const complete = (res) => {
- if (typeof options.complete === 'function') options.complete(res);
- };
-
- // #ifdef H5
- const canvas = this.getCanvasElement() || (this.ctx && this.ctx.canvas);
- if (canvas && typeof canvas.toDataURL === 'function') {
- try {
- let exportCanvas = canvas;
- if (
- request.x !== 0 ||
- request.y !== 0 ||
- request.width !== this.actualWidth ||
- request.height !== this.actualHeight ||
- request.destWidth !== request.width ||
- request.destHeight !== request.height
- ) {
- exportCanvas = document.createElement('canvas');
- exportCanvas.width = request.destWidth;
- exportCanvas.height = request.destHeight;
- const exportCtx = exportCanvas.getContext('2d');
- exportCtx.drawImage(
- canvas,
- request.x * this.dpr,
- request.y * this.dpr,
- request.width * this.dpr,
- request.height * this.dpr,
- 0,
- 0,
- request.destWidth,
- request.destHeight
- );
- }
- const mime = request.fileType === 'jpg' || request.fileType === 'jpeg'
- ? 'image/jpeg'
- : 'image/png';
- const res = {
- tempFilePath: exportCanvas.toDataURL(mime, request.quality)
- };
- success(res);
- complete(res);
- return;
- } catch (error) {
- fail(error);
- complete(error);
- return;
- }
- }
- // #endif
-
- // #ifdef APP-NVUE
- if (this.ctx && typeof this.ctx.toTempFilePath === 'function') {
- this.ctx.toTempFilePath(
- request.x,
- request.y,
- request.width,
- request.height,
- request.destWidth,
- request.destHeight,
- request.fileType,
- request.quality,
- (res) => {
- success(res);
- complete(res);
- }
- );
- return;
- }
- // #endif
-
- const canvasNode = this.getCanvasElement();
- const uniOptions = {
- ...request,
- canvasId: this.canvasId,
- success,
- fail,
- complete
- };
- if (canvasNode) {
- uniOptions.canvas = canvasNode;
- }
- uni.canvasToTempFilePath(uniOptions, this);
- });
- },
- async exportImage(fileType = 'png', quality = 1) {
- const res = await this.toTempFilePath({
- fileType,
- quality,
- width: this.actualWidth,
- height: this.actualHeight,
- destWidth: this.actualWidth,
- destHeight: this.actualHeight
- });
- return res.tempFilePath || res.apFilePath;
- },
- getImageData(options = {}) {
- return new Promise((resolve, reject) => {
- const request = {
- canvasId: this.canvasId,
- x: options.x || 0,
- y: options.y || 0,
- width: options.width || this.actualWidth,
- height: options.height || this.actualHeight,
- success: (res) => {
- if (typeof options.success === 'function') options.success(res);
- resolve(res);
- },
- fail: (err) => {
- if (typeof options.fail === 'function') options.fail(err);
- reject(err);
- },
- complete: options.complete
- };
- uni.canvasGetImageData(request, this);
- });
- },
- putImageData(options = {}) {
- return new Promise((resolve, reject) => {
- const request = {
- canvasId: this.canvasId,
- x: options.x || 0,
- y: options.y || 0,
- width: options.width || this.actualWidth,
- height: options.height || this.actualHeight,
- data: options.data,
- success: (res) => {
- if (typeof options.success === 'function') options.success(res);
- resolve(res);
- },
- fail: (err) => {
- if (typeof options.fail === 'function') options.fail(err);
- reject(err);
- },
- complete: options.complete
- };
- uni.canvasPutImageData(request, this);
- });
- }
- }
- };
- </script>
-
- <style lang="scss" scoped>
- .u-canvas {
- position: relative;
- overflow: hidden;
- }
-
- .u-canvas__canvas {
- display: block;
- width: 100%;
- height: 100%;
- }
- </style>
|