橡皮擦
This commit is contained in:
parent
b4e7867922
commit
89ef820366
|
@ -30,7 +30,7 @@
|
||||||
"electron-log": "^5.1.7",
|
"electron-log": "^5.1.7",
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.1.7",
|
||||||
"element-plus": "^2.7.6",
|
"element-plus": "^2.7.6",
|
||||||
"fabric-with-erasing": "^1.0.1",
|
"fabric": "^5.3.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"jsondiffpatch": "0.6.0",
|
"jsondiffpatch": "0.6.0",
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<svg style="fill: currentColor;color: #ccc;" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M1023 366.1L662.3 5.2 585 82.6l-34.4-34.4L69.7 538l34.4 34.4-51.5 34.4c-68.7 68.7-68.7 171.9 0 240.6l120.2 120.3c68.7 68.7 171.8 68.7 240.5 0l42.9-43 34.4 34.4 489.5-489.8-34.4-25.8 77.3-77.4zM662.3 65.4l300.6 300.8-51.5 51.6-300.6-309.5 51.5-42.9zM404.7 924.7c-60.1 60.2-154.6 60.2-214.7 0l-94.5-94.5c-60.1-60.2-60.1-154.7 0-214.8l34.4-17.2L430.5 899l-25.8 25.7z" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 502 B |
|
@ -1,10 +1,776 @@
|
||||||
/**
|
/**
|
||||||
* @description 封装fabric js
|
* @description 封装fabric js
|
||||||
*/
|
*/
|
||||||
// import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { fabric } from 'fabric-with-erasing'
|
// import { fabric } from 'fabric-with-erasing'
|
||||||
|
// import fabric from './fabric'
|
||||||
|
// import * as fabric from 'fabric'
|
||||||
import { diff, unpatch, patch } from 'jsondiffpatch'
|
import { diff, unpatch, patch } from 'jsondiffpatch'
|
||||||
|
|
||||||
|
function baseBrush() {
|
||||||
|
/** ERASER_START */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add `eraser` to enlivened props
|
||||||
|
*/
|
||||||
|
fabric.Object.ENLIVEN_PROPS.push('eraser');
|
||||||
|
|
||||||
|
var __drawClipPath = fabric.Object.prototype._drawClipPath;
|
||||||
|
var _needsItsOwnCache = fabric.Object.prototype.needsItsOwnCache;
|
||||||
|
var _toObject = fabric.Object.prototype.toObject;
|
||||||
|
var _getSvgCommons = fabric.Object.prototype.getSvgCommons;
|
||||||
|
var __createBaseClipPathSVGMarkup = fabric.Object.prototype._createBaseClipPathSVGMarkup;
|
||||||
|
var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup;
|
||||||
|
|
||||||
|
fabric.Object.prototype.cacheProperties.push('eraser');
|
||||||
|
fabric.Object.prototype.stateProperties.push('eraser');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fires erasing:end
|
||||||
|
*/
|
||||||
|
fabric.util.object.extend(fabric.Object.prototype, {
|
||||||
|
/**
|
||||||
|
* Indicates whether this object can be erased by {@link fabric.EraserBrush}
|
||||||
|
* The `deep` option introduces fine grained control over a group's `erasable` property.
|
||||||
|
* When set to `deep` the eraser will erase nested objects if they are erasable, leaving the group and the other objects untouched.
|
||||||
|
* When set to `true` the eraser will erase the entire group. Once the group changes the eraser is propagated to its children for proper functionality.
|
||||||
|
* When set to `false` the eraser will leave all objects including the group untouched.
|
||||||
|
* @tutorial {@link http://fabricjs.com/erasing#erasable_property}
|
||||||
|
* @type boolean | 'deep'
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
erasable: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @tutorial {@link http://fabricjs.com/erasing#eraser}
|
||||||
|
* @type fabric.Eraser
|
||||||
|
*/
|
||||||
|
eraser: undefined,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
|
needsItsOwnCache: function () {
|
||||||
|
return _needsItsOwnCache.call(this) || !!this.eraser;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draw eraser above clip path
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
* @param {fabric.Object} clipPath
|
||||||
|
*/
|
||||||
|
_drawClipPath: function (ctx, clipPath) {
|
||||||
|
__drawClipPath.call(this, ctx, clipPath);
|
||||||
|
if (this.eraser) {
|
||||||
|
// update eraser size to match instance
|
||||||
|
var size = this._getNonTransformedDimensions();
|
||||||
|
this.eraser.isType('eraser') && this.eraser.set({
|
||||||
|
width: size.x,
|
||||||
|
height: size.y
|
||||||
|
});
|
||||||
|
__drawClipPath.call(this, ctx, this.eraser);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object representation of an instance
|
||||||
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
||||||
|
* @return {Object} Object representation of an instance
|
||||||
|
*/
|
||||||
|
toObject: function (propertiesToInclude) {
|
||||||
|
var object = _toObject.call(this, ['erasable'].concat(propertiesToInclude));
|
||||||
|
if (this.eraser && !this.eraser.excludeFromExport) {
|
||||||
|
object.eraser = this.eraser.toObject(propertiesToInclude);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* _TO_SVG_START_ */
|
||||||
|
/**
|
||||||
|
* Returns id attribute for svg output
|
||||||
|
* @override
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
getSvgCommons: function () {
|
||||||
|
return _getSvgCommons.call(this) + (this.eraser ? 'mask="url(#' + this.eraser.clipPathId + ')" ' : '');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create svg markup for eraser
|
||||||
|
* use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
|
||||||
|
* must be called before object markup creation as it relies on the `clipPathId` property of the mask
|
||||||
|
* @param {Function} [reviver]
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
_createEraserSVGMarkup: function (reviver) {
|
||||||
|
if (this.eraser) {
|
||||||
|
this.eraser.clipPathId = 'MASK_' + fabric.Object.__uid++;
|
||||||
|
return [
|
||||||
|
'<mask id="', this.eraser.clipPathId, '" >',
|
||||||
|
this.eraser.toSVG(reviver),
|
||||||
|
'</mask>', '\n'
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createBaseClipPathSVGMarkup: function (objectMarkup, options) {
|
||||||
|
return [
|
||||||
|
this._createEraserSVGMarkup(options && options.reviver),
|
||||||
|
__createBaseClipPathSVGMarkup.call(this, objectMarkup, options)
|
||||||
|
].join('');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createBaseSVGMarkup: function (objectMarkup, options) {
|
||||||
|
return [
|
||||||
|
this._createEraserSVGMarkup(options && options.reviver),
|
||||||
|
__createBaseSVGMarkup.call(this, objectMarkup, options)
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
/* _TO_SVG_END_ */
|
||||||
|
});
|
||||||
|
|
||||||
|
var __restoreObjectsState = fabric.Group.prototype._restoreObjectsState;
|
||||||
|
fabric.util.object.extend(fabric.Group.prototype, {
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {fabric.Path} path
|
||||||
|
*/
|
||||||
|
_addEraserPathToObjects: function (path) {
|
||||||
|
this._objects.forEach(function (object) {
|
||||||
|
fabric.EraserBrush.prototype._addPathToObjectEraser.call(
|
||||||
|
fabric.EraserBrush.prototype,
|
||||||
|
object,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the group's eraser to its objects
|
||||||
|
* @tutorial {@link http://fabricjs.com/erasing#erasable_property}
|
||||||
|
*/
|
||||||
|
applyEraserToObjects: function () {
|
||||||
|
var _this = this, eraser = this.eraser;
|
||||||
|
if (eraser) {
|
||||||
|
delete this.eraser;
|
||||||
|
var transform = _this.calcTransformMatrix();
|
||||||
|
eraser.clone(function (eraser) {
|
||||||
|
var clipPath = _this.clipPath;
|
||||||
|
eraser.getObjects('path')
|
||||||
|
.forEach(function (path) {
|
||||||
|
// first we transform the path from the group's coordinate system to the canvas'
|
||||||
|
var originalTransform = fabric.util.multiplyTransformMatrices(
|
||||||
|
transform,
|
||||||
|
path.calcTransformMatrix()
|
||||||
|
);
|
||||||
|
fabric.util.applyTransformToObject(path, originalTransform);
|
||||||
|
if (clipPath) {
|
||||||
|
clipPath.clone(function (_clipPath) {
|
||||||
|
var eraserPath = fabric.EraserBrush.prototype.applyClipPathToPath.call(
|
||||||
|
fabric.EraserBrush.prototype,
|
||||||
|
path,
|
||||||
|
_clipPath,
|
||||||
|
transform
|
||||||
|
);
|
||||||
|
_this._addEraserPathToObjects(eraserPath);
|
||||||
|
}, ['absolutePositioned', 'inverted']);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_this._addEraserPathToObjects(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate the group's eraser to its objects, crucial for proper functionality of the eraser within the group and nested objects.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_restoreObjectsState: function () {
|
||||||
|
this.erasable === true && this.applyEraserToObjects();
|
||||||
|
return __restoreObjectsState.call(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object's Eraser
|
||||||
|
* @private
|
||||||
|
* @class fabric.Eraser
|
||||||
|
* @extends fabric.Group
|
||||||
|
* @memberof fabric
|
||||||
|
*/
|
||||||
|
fabric.Eraser = fabric.util.createClass(fabric.Group, {
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
type: 'eraser',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
originX: 'center',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
originY: 'center',
|
||||||
|
|
||||||
|
drawObject: function (ctx) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
||||||
|
ctx.restore();
|
||||||
|
this.callSuper('drawObject', ctx);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* eraser should retain size
|
||||||
|
* dimensions should not change when paths are added or removed
|
||||||
|
* handled by {@link fabric.Object#_drawClipPath}
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getBounds: function () {
|
||||||
|
// noop
|
||||||
|
},
|
||||||
|
|
||||||
|
/* _TO_SVG_START_ */
|
||||||
|
/**
|
||||||
|
* Returns svg representation of an instance
|
||||||
|
* use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
|
||||||
|
* for masking we need to add a white rect before all paths
|
||||||
|
*
|
||||||
|
* @param {Function} [reviver] Method for further parsing of svg representation.
|
||||||
|
* @return {String} svg representation of an instance
|
||||||
|
*/
|
||||||
|
_toSVG: function (reviver) {
|
||||||
|
var svgString = ['<g ', 'COMMON_PARTS', ' >\n'];
|
||||||
|
var x = -this.width / 2, y = -this.height / 2;
|
||||||
|
var rectSvg = [
|
||||||
|
'<rect ', 'fill="white" ',
|
||||||
|
'x="', x, '" y="', y,
|
||||||
|
'" width="', this.width, '" height="', this.height,
|
||||||
|
'" />\n'
|
||||||
|
].join('');
|
||||||
|
svgString.push('\t\t', rectSvg);
|
||||||
|
for (var i = 0, len = this._objects.length; i < len; i++) {
|
||||||
|
svgString.push('\t\t', this._objects[i].toSVG(reviver));
|
||||||
|
}
|
||||||
|
svgString.push('</g>\n');
|
||||||
|
return svgString;
|
||||||
|
},
|
||||||
|
/* _TO_SVG_END_ */
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link fabric.Eraser} instance from an object representation
|
||||||
|
* @static
|
||||||
|
* @memberOf fabric.Eraser
|
||||||
|
* @param {Object} object Object to create an Eraser from
|
||||||
|
* @param {Function} [callback] Callback to invoke when an eraser instance is created
|
||||||
|
*/
|
||||||
|
fabric.Eraser.fromObject = function (object, callback) {
|
||||||
|
var objects = object.objects;
|
||||||
|
fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
|
||||||
|
var options = fabric.util.object.clone(object, true);
|
||||||
|
delete options.objects;
|
||||||
|
fabric.util.enlivenObjectEnlivables(object, options, function () {
|
||||||
|
callback && callback(new fabric.Eraser(enlivenedObjects, options, true));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var __renderOverlay = fabric.Canvas.prototype._renderOverlay;
|
||||||
|
/**
|
||||||
|
* @fires erasing:start
|
||||||
|
* @fires erasing:end
|
||||||
|
*/
|
||||||
|
fabric.util.object.extend(fabric.Canvas.prototype, {
|
||||||
|
/**
|
||||||
|
* Used by {@link #renderAll}
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
isErasing: function () {
|
||||||
|
return (
|
||||||
|
this.isDrawingMode &&
|
||||||
|
this.freeDrawingBrush &&
|
||||||
|
this.freeDrawingBrush.type === 'eraser' &&
|
||||||
|
this.freeDrawingBrush._isErasing
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While erasing the brush clips out the erasing path from canvas
|
||||||
|
* so we need to render it on top of canvas every render
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
*/
|
||||||
|
_renderOverlay: function (ctx) {
|
||||||
|
__renderOverlay.call(this, ctx);
|
||||||
|
if (this.isErasing() && !this.freeDrawingBrush.inverted) {
|
||||||
|
this.freeDrawingBrush._render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EraserBrush class
|
||||||
|
* Supports selective erasing meaning that only erasable objects are affected by the eraser brush.
|
||||||
|
* Supports **inverted** erasing meaning that the brush can "undo" erasing.
|
||||||
|
*
|
||||||
|
* In order to support selective erasing, the brush clips the entire canvas
|
||||||
|
* and then draws all non-erasable objects over the erased path using a pattern brush so to speak (masking).
|
||||||
|
* If brush is **inverted** there is no need to clip canvas. The brush draws all erasable objects without their eraser.
|
||||||
|
* This achieves the desired effect of seeming to erase or unerase only erasable objects.
|
||||||
|
* After erasing is done the created path is added to all intersected objects' `eraser` property.
|
||||||
|
*
|
||||||
|
* In order to update the EraserBrush call `preparePattern`.
|
||||||
|
* It may come in handy when canvas changes during erasing (i.e animations) and you want the eraser to reflect the changes.
|
||||||
|
*
|
||||||
|
* @tutorial {@link http://fabricjs.com/erasing}
|
||||||
|
* @class fabric.EraserBrush
|
||||||
|
* @extends fabric.PencilBrush
|
||||||
|
* @memberof fabric
|
||||||
|
*/
|
||||||
|
fabric.EraserBrush = fabric.util.createClass(
|
||||||
|
fabric.PencilBrush,
|
||||||
|
/** @lends fabric.EraserBrush.prototype */ {
|
||||||
|
type: 'eraser',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to `true` the brush will create a visual effect of undoing erasing
|
||||||
|
*/
|
||||||
|
inverted: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_isErasing: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {fabric.Object} object
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
_isErasable: function (object) {
|
||||||
|
return object.erasable !== false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* This is designed to support erasing a collection with both erasable and non-erasable objects.
|
||||||
|
* Iterates over collections to allow nested selective erasing.
|
||||||
|
* Prepares the pattern brush that will draw on the top context to achieve the desired visual effect.
|
||||||
|
* If brush is **NOT** inverted render all non-erasable objects.
|
||||||
|
* If brush is inverted render all erasable objects that have been erased with their clip path inverted.
|
||||||
|
* This will render the erased parts as if they were not erased.
|
||||||
|
*
|
||||||
|
* @param {fabric.Collection} collection
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
* @param {{ visibility: fabric.Object[], eraser: fabric.Object[], collection: fabric.Object[] }} restorationContext
|
||||||
|
*/
|
||||||
|
_prepareCollectionTraversal: function (collection, ctx, restorationContext) {
|
||||||
|
collection.forEachObject(function (obj) {
|
||||||
|
if (obj.forEachObject && obj.erasable === 'deep') {
|
||||||
|
// traverse
|
||||||
|
this._prepareCollectionTraversal(obj, ctx, restorationContext);
|
||||||
|
}
|
||||||
|
else if (!this.inverted && obj.erasable && obj.visible) {
|
||||||
|
// render only non-erasable objects
|
||||||
|
obj.visible = false;
|
||||||
|
collection.dirty = true;
|
||||||
|
restorationContext.visibility.push(obj);
|
||||||
|
restorationContext.collection.push(collection);
|
||||||
|
}
|
||||||
|
else if (this.inverted && obj.visible) {
|
||||||
|
// render only erasable objects that were erased
|
||||||
|
if (obj.erasable && obj.eraser) {
|
||||||
|
obj.eraser.inverted = true;
|
||||||
|
obj.dirty = true;
|
||||||
|
collection.dirty = true;
|
||||||
|
restorationContext.eraser.push(obj);
|
||||||
|
restorationContext.collection.push(collection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj.visible = false;
|
||||||
|
collection.dirty = true;
|
||||||
|
restorationContext.visibility.push(obj);
|
||||||
|
restorationContext.collection.push(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the pattern for the erasing brush
|
||||||
|
* This pattern will be drawn on the top context, achieving a visual effect of erasing only erasable objects
|
||||||
|
* @todo decide how overlay color should behave when `inverted === true`, currently draws over it which is undesirable
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
preparePattern: function () {
|
||||||
|
if (!this._patternCanvas) {
|
||||||
|
this._patternCanvas = fabric.util.createCanvasElement();
|
||||||
|
}
|
||||||
|
var canvas = this._patternCanvas;
|
||||||
|
canvas.width = this.canvas.width;
|
||||||
|
canvas.height = this.canvas.height;
|
||||||
|
var patternCtx = canvas.getContext('2d');
|
||||||
|
if (this.canvas._isRetinaScaling()) {
|
||||||
|
var retinaScaling = this.canvas.getRetinaScaling();
|
||||||
|
this.canvas.__initRetinaScaling(retinaScaling, canvas, patternCtx);
|
||||||
|
}
|
||||||
|
var backgroundImage = this.canvas.backgroundImage,
|
||||||
|
bgErasable = backgroundImage && this._isErasable(backgroundImage),
|
||||||
|
overlayImage = this.canvas.overlayImage,
|
||||||
|
overlayErasable = overlayImage && this._isErasable(overlayImage);
|
||||||
|
if (!this.inverted && ((backgroundImage && !bgErasable) || !!this.canvas.backgroundColor)) {
|
||||||
|
if (bgErasable) { this.canvas.backgroundImage = undefined; }
|
||||||
|
this.canvas._renderBackground(patternCtx);
|
||||||
|
if (bgErasable) { this.canvas.backgroundImage = backgroundImage; }
|
||||||
|
}
|
||||||
|
else if (this.inverted && (backgroundImage && bgErasable)) {
|
||||||
|
var color = this.canvas.backgroundColor;
|
||||||
|
this.canvas.backgroundColor = undefined;
|
||||||
|
this.canvas._renderBackground(patternCtx);
|
||||||
|
this.canvas.backgroundColor = color;
|
||||||
|
}
|
||||||
|
patternCtx.save();
|
||||||
|
patternCtx.transform.apply(patternCtx, this.canvas.viewportTransform);
|
||||||
|
var restorationContext = { visibility: [], eraser: [], collection: [] };
|
||||||
|
this._prepareCollectionTraversal(this.canvas, patternCtx, restorationContext);
|
||||||
|
this.canvas._renderObjects(patternCtx, this.canvas._objects);
|
||||||
|
restorationContext.visibility.forEach(function (obj) { obj.visible = true; });
|
||||||
|
restorationContext.eraser.forEach(function (obj) {
|
||||||
|
obj.eraser.inverted = false;
|
||||||
|
obj.dirty = true;
|
||||||
|
});
|
||||||
|
restorationContext.collection.forEach(function (obj) { obj.dirty = true; });
|
||||||
|
patternCtx.restore();
|
||||||
|
if (!this.inverted && ((overlayImage && !overlayErasable) || !!this.canvas.overlayColor)) {
|
||||||
|
if (overlayErasable) { this.canvas.overlayImage = undefined; }
|
||||||
|
__renderOverlay.call(this.canvas, patternCtx);
|
||||||
|
if (overlayErasable) { this.canvas.overlayImage = overlayImage; }
|
||||||
|
}
|
||||||
|
else if (this.inverted && (overlayImage && overlayErasable)) {
|
||||||
|
var color = this.canvas.overlayColor;
|
||||||
|
this.canvas.overlayColor = undefined;
|
||||||
|
__renderOverlay.call(this.canvas, patternCtx);
|
||||||
|
this.canvas.overlayColor = color;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets brush styles
|
||||||
|
* @private
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
*/
|
||||||
|
_setBrushStyles: function (ctx) {
|
||||||
|
this.callSuper('_setBrushStyles', ctx);
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Customiztion**
|
||||||
|
*
|
||||||
|
* if you need the eraser to update on each render (i.e animating during erasing) override this method by **adding** the following (performance may suffer):
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* if(ctx === this.canvas.contextTop) {
|
||||||
|
* this.preparePattern();
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @override fabric.BaseBrush#_saveAndTransform
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
*/
|
||||||
|
_saveAndTransform: function (ctx) {
|
||||||
|
this.callSuper('_saveAndTransform', ctx);
|
||||||
|
this._setBrushStyles(ctx);
|
||||||
|
ctx.globalCompositeOperation = ctx === this.canvas.getContext() ? 'destination-out' : 'source-over';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We indicate {@link fabric.PencilBrush} to repaint itself if necessary
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
needsFullRender: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {fabric.Point} pointer
|
||||||
|
* @param {fabric.IEvent} options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onMouseDown: function (pointer, options) {
|
||||||
|
if (!this.canvas._isMainEvent(options.e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._prepareForDrawing(pointer);
|
||||||
|
// capture coordinates immediately
|
||||||
|
// this allows to draw dots (when movement never occurs)
|
||||||
|
this._captureDrawingPath(pointer);
|
||||||
|
|
||||||
|
// prepare for erasing
|
||||||
|
this.preparePattern();
|
||||||
|
this._isErasing = true;
|
||||||
|
this.canvas.fire('erasing:start');
|
||||||
|
this._render();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendering Logic:
|
||||||
|
* 1. Use brush to clip canvas by rendering it on top of canvas (unnecessary if `inverted === true`)
|
||||||
|
* 2. Render brush with canvas pattern on top context
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_render: function () {
|
||||||
|
var ctx;
|
||||||
|
if (!this.inverted) {
|
||||||
|
// clip canvas
|
||||||
|
ctx = this.canvas.getContext();
|
||||||
|
this.callSuper('_render', ctx);
|
||||||
|
}
|
||||||
|
// render brush and mask it with image of non erasables
|
||||||
|
ctx = this.canvas.contextTop;
|
||||||
|
this.canvas.clearContext(ctx);
|
||||||
|
this.callSuper('_render', ctx);
|
||||||
|
ctx.save();
|
||||||
|
var t = this.canvas.getRetinaScaling(), s = 1 / t;
|
||||||
|
ctx.scale(s, s);
|
||||||
|
ctx.globalCompositeOperation = 'source-in';
|
||||||
|
ctx.drawImage(this._patternCanvas, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates fabric.Path object
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
* @param {(string|number)[][]} pathData Path data
|
||||||
|
* @return {fabric.Path} Path to add on canvas
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
createPath: function (pathData) {
|
||||||
|
var path = this.callSuper('createPath', pathData);
|
||||||
|
path.globalCompositeOperation = this.inverted ? 'source-over' : 'destination-out';
|
||||||
|
path.stroke = this.inverted ? 'white' : 'black';
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to apply a clip path to a path.
|
||||||
|
* Used to preserve clipping on eraser paths in nested objects.
|
||||||
|
* Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
|
||||||
|
* @param {fabric.Path} path The eraser path in canvas coordinate plane
|
||||||
|
* @param {fabric.Object} clipPath The clipPath to apply to the path
|
||||||
|
* @param {number[]} clipPathContainerTransformMatrix The transform matrix of the object that the clip path belongs to
|
||||||
|
* @returns {fabric.Path} path with clip path
|
||||||
|
*/
|
||||||
|
applyClipPathToPath: function (path, clipPath, clipPathContainerTransformMatrix) {
|
||||||
|
var pathInvTransform = fabric.util.invertTransform(path.calcTransformMatrix()),
|
||||||
|
clipPathTransform = clipPath.calcTransformMatrix(),
|
||||||
|
transform = clipPath.absolutePositioned ?
|
||||||
|
pathInvTransform :
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
pathInvTransform,
|
||||||
|
clipPathContainerTransformMatrix
|
||||||
|
);
|
||||||
|
// when passing down a clip path it becomes relative to the parent
|
||||||
|
// so we transform it acoordingly and set `absolutePositioned` to false
|
||||||
|
clipPath.absolutePositioned = false;
|
||||||
|
fabric.util.applyTransformToObject(
|
||||||
|
clipPath,
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
transform,
|
||||||
|
clipPathTransform
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// We need to clip `path` with both `clipPath` and it's own clip path if existing (`path.clipPath`)
|
||||||
|
// so in turn `path` erases an object only where it overlaps with all it's clip paths, regardless of how many there are.
|
||||||
|
// this is done because both clip paths may have nested clip paths of their own (this method walks down a collection => this may reccur),
|
||||||
|
// so we can't assign one to the other's clip path property.
|
||||||
|
path.clipPath = path.clipPath ? fabric.util.mergeClipPaths(clipPath, path.clipPath) : clipPath;
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to apply a clip path to a path.
|
||||||
|
* Used to preserve clipping on eraser paths in nested objects.
|
||||||
|
* Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects.
|
||||||
|
* @param {fabric.Path} path The eraser path
|
||||||
|
* @param {fabric.Object} object The clipPath to apply to path belongs to object
|
||||||
|
* @param {Function} callback Callback to be invoked with the cloned path after applying the clip path
|
||||||
|
*/
|
||||||
|
clonePathWithClipPath: function (path, object, callback) {
|
||||||
|
var objTransform = object.calcTransformMatrix();
|
||||||
|
var clipPath = object.clipPath;
|
||||||
|
var _this = this;
|
||||||
|
path.clone(function (_path) {
|
||||||
|
clipPath.clone(function (_clipPath) {
|
||||||
|
callback(_this.applyClipPathToPath(_path, _clipPath, objTransform));
|
||||||
|
}, ['absolutePositioned', 'inverted']);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds path to object's eraser, walks down object's descendants if necessary
|
||||||
|
*
|
||||||
|
* @fires erasing:end on object
|
||||||
|
* @param {fabric.Object} obj
|
||||||
|
* @param {fabric.Path} path
|
||||||
|
*/
|
||||||
|
_addPathToObjectEraser: function (obj, path) {
|
||||||
|
var _this = this;
|
||||||
|
// object is collection, i.e group
|
||||||
|
if (obj.forEachObject && obj.erasable === 'deep') {
|
||||||
|
var targets = obj._objects.filter(function (_obj) {
|
||||||
|
return _obj.erasable;
|
||||||
|
});
|
||||||
|
if (targets.length > 0 && obj.clipPath) {
|
||||||
|
this.clonePathWithClipPath(path, obj, function (_path) {
|
||||||
|
targets.forEach(function (_obj) {
|
||||||
|
_this._addPathToObjectEraser(_obj, _path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (targets.length > 0) {
|
||||||
|
targets.forEach(function (_obj) {
|
||||||
|
_this._addPathToObjectEraser(_obj, path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// prepare eraser
|
||||||
|
var eraser = obj.eraser;
|
||||||
|
if (!eraser) {
|
||||||
|
eraser = new fabric.Eraser();
|
||||||
|
obj.eraser = eraser;
|
||||||
|
}
|
||||||
|
// clone and add path
|
||||||
|
path.clone(function (path) {
|
||||||
|
// http://fabricjs.com/using-transformations
|
||||||
|
var desiredTransform = fabric.util.multiplyTransformMatrices(
|
||||||
|
fabric.util.invertTransform(
|
||||||
|
obj.calcTransformMatrix()
|
||||||
|
),
|
||||||
|
path.calcTransformMatrix()
|
||||||
|
);
|
||||||
|
fabric.util.applyTransformToObject(path, desiredTransform);
|
||||||
|
eraser.addWithUpdate(path);
|
||||||
|
obj.set('dirty', true);
|
||||||
|
obj.fire('erasing:end', {
|
||||||
|
path: path
|
||||||
|
});
|
||||||
|
if (obj.group && Array.isArray(_this.__subTargets)) {
|
||||||
|
_this.__subTargets.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the eraser path to canvas drawables' clip paths
|
||||||
|
*
|
||||||
|
* @param {fabric.Canvas} source
|
||||||
|
* @param {fabric.Canvas} path
|
||||||
|
* @returns {Object} canvas drawables that were erased by the path
|
||||||
|
*/
|
||||||
|
applyEraserToCanvas: function (path) {
|
||||||
|
var canvas = this.canvas;
|
||||||
|
var drawables = {};
|
||||||
|
[
|
||||||
|
'backgroundImage',
|
||||||
|
'overlayImage',
|
||||||
|
].forEach(function (prop) {
|
||||||
|
var drawable = canvas[prop];
|
||||||
|
if (drawable && drawable.erasable) {
|
||||||
|
this._addPathToObjectEraser(drawable, path);
|
||||||
|
drawables[prop] = drawable;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
return drawables;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mouseup after drawing the path on contextTop canvas
|
||||||
|
* we use the points captured to create an new fabric path object
|
||||||
|
* and add it to every intersected erasable object.
|
||||||
|
*/
|
||||||
|
_finalizeAndAddPath: function () {
|
||||||
|
var ctx = this.canvas.contextTop, canvas = this.canvas;
|
||||||
|
ctx.closePath();
|
||||||
|
if (this.decimate) {
|
||||||
|
this._points = this.decimatePoints(this._points, this.decimate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear
|
||||||
|
canvas.clearContext(canvas.contextTop);
|
||||||
|
this._isErasing = false;
|
||||||
|
|
||||||
|
var pathData = this._points && this._points.length > 1 ?
|
||||||
|
this.convertPointsToSVGPath(this._points) :
|
||||||
|
null;
|
||||||
|
if (!pathData || this._isEmptySVGPath(pathData)) {
|
||||||
|
canvas.fire('erasing:end');
|
||||||
|
// do not create 0 width/height paths, as they are
|
||||||
|
// rendered inconsistently across browsers
|
||||||
|
// Firefox 4, for example, renders a dot,
|
||||||
|
// whereas Chrome 10 renders nothing
|
||||||
|
canvas.requestRenderAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = this.createPath(pathData);
|
||||||
|
// needed for `intersectsWithObject`
|
||||||
|
path.setCoords();
|
||||||
|
// commense event sequence
|
||||||
|
canvas.fire('before:path:created', { path: path });
|
||||||
|
|
||||||
|
// finalize erasing
|
||||||
|
var drawables = this.applyEraserToCanvas(path);
|
||||||
|
var _this = this;
|
||||||
|
this.__subTargets = [];
|
||||||
|
var targets = [];
|
||||||
|
canvas.forEachObject(function (obj) {
|
||||||
|
if (obj.erasable && obj.intersectsWithObject(path, true, true)) {
|
||||||
|
_this._addPathToObjectEraser(obj, path);
|
||||||
|
targets.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// fire erasing:end
|
||||||
|
canvas.fire('erasing:end', {
|
||||||
|
path: path,
|
||||||
|
targets: targets,
|
||||||
|
subTargets: this.__subTargets,
|
||||||
|
drawables: drawables
|
||||||
|
});
|
||||||
|
delete this.__subTargets;
|
||||||
|
|
||||||
|
canvas.requestRenderAll();
|
||||||
|
this._resetShadow();
|
||||||
|
|
||||||
|
// fire event 'path' created
|
||||||
|
canvas.fire('path:created', { path: path });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/** ERASER_END */
|
||||||
|
}
|
||||||
|
baseBrush() // 加载橡皮擦到组件上
|
||||||
// 当前使用到的常量|类型(枚举) ============================
|
// 当前使用到的常量|类型(枚举) ============================
|
||||||
export class TYPES {
|
export class TYPES {
|
||||||
static ActionMode = {
|
static ActionMode = {
|
||||||
|
@ -378,10 +1144,21 @@ export const FreeStyle = {
|
||||||
const canvas = fabricVue?.canvas
|
const canvas = fabricVue?.canvas
|
||||||
const drawConfig = fabricVue?.drawConfig
|
const drawConfig = fabricVue?.drawConfig
|
||||||
const eraserBrush = new fabric.EraserBrush(canvas)
|
const eraserBrush = new fabric.EraserBrush(canvas)
|
||||||
|
const width = Utils.getWidth(drawConfig.eraserWidth, fabricVue)
|
||||||
canvas.isDrawingMode = true
|
canvas.isDrawingMode = true
|
||||||
canvas.freeDrawingBrush = eraserBrush
|
canvas.freeDrawingBrush = eraserBrush
|
||||||
canvas.freeDrawingBrush.width = Utils.getWidth(drawConfig.eraserWidth, fabricVue)
|
canvas.freeDrawingBrush.width = width
|
||||||
canvas.freeDrawingBrush.color = '#FFF'
|
canvas.freeDrawingBrush.color = '#FFF'
|
||||||
|
// FabricVue.canvas.freeDrawingCursor = `url(/imgs/erase.svg) 10 10,crosshair`
|
||||||
|
canvas.freeDrawingCursor = FreeStyle.reaserSvg(width)
|
||||||
|
},
|
||||||
|
reaserSvg: (width, color = '#ccc') => { // 橡皮擦-鼠标样式(自定义)
|
||||||
|
const svg = `
|
||||||
|
<svg style="fill: currentColor;color: ${color};" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1023 366.1L662.3 5.2 585 82.6l-34.4-34.4L69.7 538l34.4 34.4-51.5 34.4c-68.7 68.7-68.7 171.9 0 240.6l120.2 120.3c68.7 68.7 171.8 68.7 240.5 0l42.9-43 34.4 34.4 489.5-489.8-34.4-25.8 77.3-77.4zM662.3 65.4l300.6 300.8-51.5 51.6-300.6-309.5 51.5-42.9zM404.7 924.7c-60.1 60.2-154.6 60.2-214.7 0l-94.5-94.5c-60.1-60.2-60.1-154.7 0-214.8l34.4-17.2L430.5 899l-25.8 25.7z" />
|
||||||
|
</svg>`
|
||||||
|
const svgUrl = `data:image/svg+xml;base64,${btoa(svg)}`
|
||||||
|
return `url(${svgUrl}) ${width/2} ${width}, crosshair`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 事件类
|
// 事件类
|
||||||
|
@ -600,8 +1377,8 @@ export class CanvasEvent {
|
||||||
}
|
}
|
||||||
// 移除事件
|
// 移除事件
|
||||||
removeEvent() {
|
removeEvent() {
|
||||||
this.windowEvent.removeWindowEvent()
|
this.windowEvent?.removeWindowEvent()
|
||||||
this.touchEvent.removeTouchEvent()
|
this.touchEvent?.removeTouchEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 历史类
|
// 历史类
|
||||||
|
@ -860,7 +1637,7 @@ export class fabricVue {
|
||||||
*/
|
*/
|
||||||
deleteObject() {
|
deleteObject() {
|
||||||
// Disable deletion in text input state
|
// Disable deletion in text input state
|
||||||
if (this.textElement.isTextEditing) {
|
if (this.textElement?.isTextEditing) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
|
@ -1010,5 +1787,4 @@ export class fabricVue {
|
||||||
}
|
}
|
||||||
export const FabricVue = new fabricVue()
|
export const FabricVue = new fabricVue()
|
||||||
export default FabricVue
|
export default FabricVue
|
||||||
export const Fabric = fabric
|
|
||||||
|
|
||||||
|
|
|
@ -5,19 +5,21 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
// 功能说明:画板
|
// 功能说明:画板
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { FabricVue } from '@/plugins/fabric'
|
import {FabricVue, TYPES} from '@/plugins/fabric'
|
||||||
import { useBoardStore, useDrawStore } from '@/store/modules/draw'
|
import { useBoardStore, useDrawStore } from '@/store/modules/draw'
|
||||||
const canvasRef = ref(null)
|
const canvasRef = ref(null)
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async() => {
|
onMounted(async() => {
|
||||||
if (canvasRef.value) {
|
if (canvasRef.value) {
|
||||||
useBoardStore().backgroundColor = 'transparent'
|
// useBoardStore().backgroundColor = 'transparent'
|
||||||
useDrawStore().drawColors = ['red']
|
// useDrawStore().drawColors = ['red']
|
||||||
|
FabricVue.drawConfig.drawColors = ['red']
|
||||||
|
FabricVue.boardConfig.backgroundColor = 'transparent'
|
||||||
const option = { freeDrawingCursor: 'default' }
|
const option = { freeDrawingCursor: 'default' }
|
||||||
await FabricVue.initCanvas(canvasRef.value, option)
|
await FabricVue.initCanvas(canvasRef.value, option)
|
||||||
// FabricVue.canvas.backgroundColor = 'transparent'
|
|
||||||
// FabricVue.canvas.setWidth(500)
|
|
||||||
// FabricVue.canvas.setHeight(500)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="warp-all">
|
<div class="warp-all">
|
||||||
<board-vue></board-vue>
|
<board-vue v-model="tabActive"></board-vue>
|
||||||
<!-- 底部工具栏 -->
|
<!-- 底部工具栏 -->
|
||||||
<el-row id="test" class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
<el-row id="test" class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
||||||
<el-col :span="3" class="flex justify-center items-center">
|
<el-col :span="3" class="flex justify-center items-center">
|
||||||
|
@ -47,6 +47,7 @@ const tabChange = (val) => { // 切换tab-change
|
||||||
case 'brush':
|
case 'brush':
|
||||||
break
|
break
|
||||||
case 'eraser':
|
case 'eraser':
|
||||||
|
|
||||||
break
|
break
|
||||||
case 'interact':
|
case 'interact':
|
||||||
break
|
break
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<canvas ref="canvasRef" />
|
<canvas ref="canvasRef" />
|
||||||
<button @click="eraseTo">橡皮擦
|
<button @click="eraseTo">橡皮擦
|
||||||
<i class="iconfont icon-xiangpica"></i>
|
<i class="iconfont icon-xiangpica"></i>
|
||||||
<img src="/imgs/erase.svg" alt="" srcset="">
|
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="close">销毁</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -11,63 +11,38 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
// import { FabricVue } from '@/plugins/fabric'
|
// import { FabricVue } from '@/plugins/fabric'
|
||||||
// import { useBoardStore } from '@/store/modules/draw'
|
// import { useBoardStore } from '@/store/modules/draw'
|
||||||
import {Fabric,FabricVue, TYPES} from '@/plugins/fabric'
|
import {FabricVue, TYPES} from '@/plugins/fabric'
|
||||||
let canvasRef = ref(null)
|
let canvasRef = ref(null)
|
||||||
|
let canvas = null
|
||||||
|
|
||||||
onMounted(async() => {
|
onMounted(async() => {
|
||||||
console.log(canvasRef, FabricVue)
|
// console.log(canvasRef, FabricVue)
|
||||||
|
// canvasRef.value = 123
|
||||||
if (canvasRef.value) {
|
if (canvasRef.value) {
|
||||||
// useBoardStore().backgroundColor = 'transparent'
|
// useBoardStore().backgroundColor = 'transparent'
|
||||||
const option = { freeDrawingCursor: 'default' }
|
const option = { freeDrawingCursor: 'default' }
|
||||||
// await FabricVue.initCanvas(canvasRef.value, option)
|
await FabricVue.initCanvas(canvasRef.value, option)
|
||||||
// FabricVue.canvas.setWidth(500)
|
// FabricVue.canvas.setWidth(500)
|
||||||
// FabricVue.canvas.setHeight(500)
|
// FabricVue.canvas.setHeight(500)
|
||||||
FabricVue.drawConfig.drawColors = ['red']
|
|
||||||
await FabricVue.initCanvas(canvasRef.value, option)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// if (canvasRef.value) {
|
||||||
|
// canvas = new fabric.Canvas(canvasRef.value,{
|
||||||
|
// isDrawingMode: true,
|
||||||
|
// freeDrawingCursor: 'default',
|
||||||
|
// backgroundColor: 'transparent',
|
||||||
|
// width: window.innerWidth,
|
||||||
|
// height: window.innerHeight
|
||||||
|
// })
|
||||||
|
// canvas.isDrawingMode = true
|
||||||
|
// }
|
||||||
})
|
})
|
||||||
const eraseTo = () => { // 橡皮擦
|
const eraseTo = () => { // 橡皮擦
|
||||||
FabricVue.handleMode(TYPES.ActionMode.ERASE)
|
FabricVue.handleMode(TYPES.ActionMode.ERASE)
|
||||||
const brush = FabricVue.canvas.freeDrawingBrush
|
// FabricVue.removeCanvas()
|
||||||
console.log('brush', brush)
|
// FabricVue.canvas.dispose()
|
||||||
// FabricVue.canvas.freeDrawingCursor = `url(/imgs/erase.svg),crosshair `
|
// canvas.dispose()
|
||||||
let tempRect
|
|
||||||
FabricVue.canvas.on('mouse:down', (e) => {
|
|
||||||
})
|
|
||||||
FabricVue.canvas.on('mouse:move', (e) => {
|
|
||||||
|
|
||||||
if (tempRect) {
|
|
||||||
tempRect.set({
|
|
||||||
left: e.pointer.x - brush.width/2,
|
|
||||||
top: e.pointer.y - brush.width/2,
|
|
||||||
})
|
|
||||||
FabricVue.canvas.renderAll()
|
|
||||||
} else {
|
|
||||||
tempRect = new Fabric.Rect({
|
|
||||||
left: e.pointer.x - brush.width/2,
|
|
||||||
top: e.pointer.y - brush.width/2,
|
|
||||||
width: brush.width,
|
|
||||||
height: brush.width,
|
|
||||||
fill: 'transparent',
|
|
||||||
stroke: 'red',
|
|
||||||
strokeWidth: 1,
|
|
||||||
hasControls: false,
|
|
||||||
hasBorders: false,
|
|
||||||
selectable: false,
|
|
||||||
evented: false
|
|
||||||
})
|
|
||||||
FabricVue.canvas.add(tempRect)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
FabricVue.canvas.on('mouse:up', (e) => {
|
|
||||||
if (tempRect) {
|
|
||||||
FabricVue.canvas.remove(tempRect);
|
|
||||||
tempRect = null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
const close = () => { FabricVue.removeCanvas() }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
Loading…
Reference in New Issue