fabric 4.5.1-browser → 5.0.0-browser

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/CONTRIBUTING.md +14 -0
  3. package/HEADER.js +1 -1
  4. package/README.md +3 -6
  5. package/SECURITY.md +5 -0
  6. package/build.js +1 -0
  7. package/dist/fabric.js +1160 -668
  8. package/dist/fabric.min.js +1 -1
  9. package/lib/event.js +7 -3
  10. package/package.json +9 -3
  11. package/publish-next.js +15 -0
  12. package/publish.js +3 -0
  13. package/src/brushes/base_brush.class.js +3 -5
  14. package/src/brushes/pattern_brush.class.js +7 -5
  15. package/src/brushes/pencil_brush.class.js +49 -37
  16. package/src/canvas.class.js +27 -57
  17. package/src/filters/saturate_filter.class.js +9 -1
  18. package/src/filters/vibrance_filter.class.js +122 -0
  19. package/src/mixins/animation.mixin.js +20 -25
  20. package/src/mixins/canvas_events.mixin.js +30 -59
  21. package/src/mixins/canvas_grouping.mixin.js +4 -4
  22. package/src/mixins/collection.mixin.js +11 -2
  23. package/src/mixins/eraser_brush.mixin.js +495 -452
  24. package/src/mixins/itext_behavior.mixin.js +7 -1
  25. package/src/mixins/itext_key_behavior.mixin.js +7 -1
  26. package/src/mixins/object_geometry.mixin.js +5 -35
  27. package/src/mixins/object_interactivity.mixin.js +18 -7
  28. package/src/mixins/object_straightening.mixin.js +4 -9
  29. package/src/mixins/observable.mixin.js +22 -0
  30. package/src/parser.js +13 -9
  31. package/src/shapes/circle.class.js +22 -19
  32. package/src/shapes/ellipse.class.js +2 -2
  33. package/src/shapes/group.class.js +24 -32
  34. package/src/shapes/image.class.js +3 -25
  35. package/src/shapes/itext.class.js +10 -0
  36. package/src/shapes/line.class.js +5 -21
  37. package/src/shapes/object.class.js +67 -32
  38. package/src/shapes/path.class.js +17 -18
  39. package/src/shapes/polygon.class.js +11 -10
  40. package/src/shapes/polyline.class.js +33 -24
  41. package/src/shapes/rect.class.js +0 -18
  42. package/src/shapes/text.class.js +120 -58
  43. package/src/shapes/triangle.class.js +0 -15
  44. package/src/static_canvas.class.js +43 -20
  45. package/src/util/animate.js +149 -16
  46. package/src/util/animate_color.js +2 -1
  47. package/src/util/lang_object.js +5 -1
  48. package/src/util/misc.js +193 -42
  49. package/src/util/path.js +89 -52
@@ -12,7 +12,8 @@
12
12
 
13
13
  var additionalProps =
14
14
  ('fontFamily fontWeight fontSize text underline overline linethrough' +
15
- ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles path').split(' ');
15
+ ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
16
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
16
17
 
17
18
  /**
18
19
  * Text class
@@ -39,7 +40,10 @@
39
40
  'charSpacing',
40
41
  'textAlign',
41
42
  'styles',
42
- 'path'
43
+ 'path',
44
+ 'pathStartOffset',
45
+ 'pathSide',
46
+ 'pathAlign'
43
47
  ],
44
48
 
45
49
  /**
@@ -197,13 +201,55 @@
197
201
  shadow: null,
198
202
 
199
203
  /**
200
- * fabric.Path that the text can follow.
201
- * This feature is in BETA.
204
+ * fabric.Path that the text should follow.
205
+ * since 4.6.0 the path will be drawn automatically.
206
+ * if you want to make the path visible, give it a stroke and strokeWidth or fill value
207
+ * if you want it to be hidden, assign visible = false to the path.
208
+ * This feature is in BETA, and SVG import/export is not yet supported.
202
209
  * @type fabric.Path
210
+ * @example
211
+ * var textPath = new fabric.Text('Text on a path', {
212
+ * top: 150,
213
+ * left: 150,
214
+ * textAlign: 'center',
215
+ * charSpacing: -50,
216
+ * path: new fabric.Path('M 0 0 C 50 -100 150 -100 200 0', {
217
+ * strokeWidth: 1,
218
+ * visible: false
219
+ * }),
220
+ * pathSide: 'left',
221
+ * pathStartOffset: 0
222
+ * });
203
223
  * @default
204
224
  */
205
225
  path: null,
206
226
 
227
+ /**
228
+ * Offset amount for text path starting position
229
+ * Only used when text has a path
230
+ * @type Number
231
+ * @default
232
+ */
233
+ pathStartOffset: 0,
234
+
235
+ /**
236
+ * Which side of the path the text should be drawn on.
237
+ * Only used when text has a path
238
+ * @type {String} 'left|right'
239
+ * @default
240
+ */
241
+ pathSide: 'left',
242
+
243
+ /**
244
+ * How text is aligned to the path. This property determines
245
+ * the perpendicular position of each character relative to the path.
246
+ * (one of "baseline", "center", "ascender", "descender")
247
+ * This feature is in BETA, and its behavior may change
248
+ * @type String
249
+ * @default
250
+ */
251
+ pathAlign: 'baseline',
252
+
207
253
  /**
208
254
  * @private
209
255
  */
@@ -347,6 +393,8 @@
347
393
  /**
348
394
  * Return a context for measurement of text string.
349
395
  * if created it gets stored for reuse
396
+ * this is for internal use, please do not use it
397
+ * @private
350
398
  * @param {String} text Text string
351
399
  * @param {Object} [options] Options object
352
400
  * @return {fabric.Text} thisArg
@@ -482,6 +530,8 @@
482
530
  * @param {CanvasRenderingContext2D} ctx Context to render on
483
531
  */
484
532
  _render: function(ctx) {
533
+ var path = this.path;
534
+ path && !path.isNotVisible() && path._render(ctx);
485
535
  this._setTextStyles(ctx);
486
536
  this._renderTextLinesBackground(ctx);
487
537
  this._renderTextDecoration(ctx, 'underline');
@@ -516,7 +566,20 @@
516
566
  * @param {String} [charStyle.fontStyle] Font style (italic|normal)
517
567
  */
518
568
  _setTextStyles: function(ctx, charStyle, forMeasuring) {
519
- ctx.textBaseline = 'alphabetic';
569
+ ctx.textBaseline = 'alphabetical';
570
+ if (this.path) {
571
+ switch (this.pathAlign) {
572
+ case 'center':
573
+ ctx.textBaseline = 'middle';
574
+ break;
575
+ case 'ascender':
576
+ ctx.textBaseline = 'top';
577
+ break;
578
+ case 'descender':
579
+ ctx.textBaseline = 'bottom';
580
+ break;
581
+ }
582
+ }
520
583
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
521
584
  },
522
585
 
@@ -741,29 +804,15 @@
741
804
  _measureLine: function(lineIndex) {
742
805
  var width = 0, i, grapheme, line = this._textLines[lineIndex], prevGrapheme,
743
806
  graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length),
744
- positionInPath = 0, startingPoint, totalPathLength, path = this.path;
807
+ positionInPath = 0, startingPoint, totalPathLength, path = this.path,
808
+ reverse = this.pathSide === 'right';
745
809
 
746
810
  this.__charBounds[lineIndex] = lineBounds;
747
- if (path) {
748
- startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo);
749
- totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length;
750
- startingPoint.x += path.pathOffset.x;
751
- startingPoint.y += path.pathOffset.y;
752
- }
753
811
  for (i = 0; i < line.length; i++) {
754
812
  grapheme = line[i];
755
813
  graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme);
756
- if (path) {
757
- if (positionInPath > totalPathLength) {
758
- positionInPath %= totalPathLength;
759
- }
760
- // it would probably much fater to send all the grapheme position for a line
761
- // and calculate path position/angle at once.
762
- this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint);
763
- }
764
814
  lineBounds[i] = graphemeInfo;
765
815
  width += graphemeInfo.kernedWidth;
766
- positionInPath += graphemeInfo.kernedWidth;
767
816
  prevGrapheme = grapheme;
768
817
  }
769
818
  // this latest bound box represent the last character of the line
@@ -774,6 +823,40 @@
774
823
  kernedWidth: 0,
775
824
  height: this.fontSize
776
825
  };
826
+ if (path) {
827
+ totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length;
828
+ startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo);
829
+ startingPoint.x += path.pathOffset.x;
830
+ startingPoint.y += path.pathOffset.y;
831
+ switch (this.textAlign) {
832
+ case 'left':
833
+ positionInPath = reverse ? (totalPathLength - width) : 0;
834
+ break;
835
+ case 'center':
836
+ positionInPath = (totalPathLength - width) / 2;
837
+ break;
838
+ case 'right':
839
+ positionInPath = reverse ? 0 : (totalPathLength - width);
840
+ break;
841
+ //todo - add support for justify
842
+ }
843
+ positionInPath += this.pathStartOffset * (reverse ? -1 : 1);
844
+ for (i = reverse ? line.length - 1 : 0;
845
+ reverse ? i >= 0 : i < line.length;
846
+ reverse ? i-- : i++) {
847
+ graphemeInfo = lineBounds[i];
848
+ if (positionInPath > totalPathLength) {
849
+ positionInPath %= totalPathLength;
850
+ }
851
+ else if (positionInPath < 0) {
852
+ positionInPath += totalPathLength;
853
+ }
854
+ // it would probably much faster to send all the grapheme position for a line
855
+ // and calculate path position/angle at once.
856
+ this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint);
857
+ positionInPath += graphemeInfo.kernedWidth;
858
+ }
859
+ }
777
860
  return { width: width, numOfSpaces: numOfSpaces };
778
861
  },
779
862
 
@@ -793,7 +876,7 @@
793
876
  var info = fabric.util.getPointOnPath(path.path, centerPosition, path.segmentsInfo);
794
877
  graphemeInfo.renderLeft = info.x - startingPoint.x;
795
878
  graphemeInfo.renderTop = info.y - startingPoint.y;
796
- graphemeInfo.angle = info.angle;
879
+ graphemeInfo.angle = info.angle + (this.pathSide === 'right' ? Math.PI : 0);
797
880
  },
798
881
 
799
882
  /**
@@ -961,16 +1044,17 @@
961
1044
  path = this.path,
962
1045
  shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
963
1046
  isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
964
- drawingLeft;
965
-
1047
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
966
1048
  ctx.save();
1049
+ if (currentDirection !== this.direction) {
1050
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1051
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
1052
+ ctx.textAlign = isLtr ? 'left' : 'right';
1053
+ }
967
1054
  top -= lineHeight * this._fontSizeFraction / this.lineHeight;
968
1055
  if (shortCut) {
969
1056
  // render all the line in one pass without checking
970
1057
  // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
971
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
972
- ctx.direction = isLtr ? 'ltr' : 'rtl';
973
- ctx.textAlign = isLtr ? 'left' : 'right';
974
1058
  this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
975
1059
  ctx.restore();
976
1060
  return;
@@ -1007,9 +1091,6 @@
1007
1091
  }
1008
1092
  else {
1009
1093
  drawingLeft = left;
1010
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1011
- ctx.direction = isLtr ? 'ltr' : 'rtl';
1012
- ctx.textAlign = isLtr ? 'left' : 'right';
1013
1094
  this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
1014
1095
  }
1015
1096
  charsToRender = '';
@@ -1260,19 +1341,12 @@
1260
1341
  * @return {Number} Line width
1261
1342
  */
1262
1343
  getLineWidth: function(lineIndex) {
1263
- if (this.__lineWidths[lineIndex]) {
1344
+ if (this.__lineWidths[lineIndex] !== undefined) {
1264
1345
  return this.__lineWidths[lineIndex];
1265
1346
  }
1266
1347
 
1267
- var width, line = this._textLines[lineIndex], lineInfo;
1268
-
1269
- if (line === '') {
1270
- width = 0;
1271
- }
1272
- else {
1273
- lineInfo = this.measureLine(lineIndex);
1274
- width = lineInfo.width;
1275
- }
1348
+ var lineInfo = this.measureLine(lineIndex);
1349
+ var width = lineInfo.width;
1276
1350
  this.__lineWidths[lineIndex] = width;
1277
1351
  return width;
1278
1352
  },
@@ -1462,25 +1536,13 @@
1462
1536
  * @return {Object} Object representation of an instance
1463
1537
  */
1464
1538
  toObject: function(propertiesToInclude) {
1465
- var additionalProperties = [
1466
- 'text',
1467
- 'fontSize',
1468
- 'fontWeight',
1469
- 'fontFamily',
1470
- 'fontStyle',
1471
- 'lineHeight',
1472
- 'underline',
1473
- 'overline',
1474
- 'linethrough',
1475
- 'textAlign',
1476
- 'textBackgroundColor',
1477
- 'charSpacing',
1478
- 'path',
1479
- 'direction',
1480
- ].concat(propertiesToInclude);
1481
- var obj = this.callSuper('toObject', additionalProperties);
1539
+ var allProperties = additionalProps.concat(propertiesToInclude);
1540
+ var obj = this.callSuper('toObject', allProperties);
1541
+ // styles will be overridden with a properly cloned structure
1482
1542
  obj.styles = clone(this.styles, true);
1483
- obj.path = this.path && this.path.toObject();
1543
+ if (obj.path) {
1544
+ obj.path = this.path.toObject();
1545
+ }
1484
1546
  return obj;
1485
1547
  },
1486
1548
 
@@ -56,21 +56,6 @@
56
56
  this._renderPaintInOrder(ctx);
57
57
  },
58
58
 
59
- /**
60
- * @private
61
- * @param {CanvasRenderingContext2D} ctx Context to render on
62
- */
63
- _renderDashedStroke: function(ctx) {
64
- var widthBy2 = this.width / 2,
65
- heightBy2 = this.height / 2;
66
-
67
- ctx.beginPath();
68
- fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
69
- fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
70
- fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
71
- ctx.closePath();
72
- },
73
-
74
59
  /* _TO_SVG_START_ */
75
60
  /**
76
61
  * Returns svg representation of an instance
@@ -133,8 +133,12 @@
133
133
  imageSmoothingEnabled: true,
134
134
 
135
135
  /**
136
- * The transformation (in the format of Canvas transform) which focuses the viewport
136
+ * The transformation (a Canvas 2D API transform matrix) which focuses the viewport
137
137
  * @type Array
138
+ * @example <caption>Default transform</caption>
139
+ * canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
140
+ * @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption>
141
+ * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50];
138
142
  * @default
139
143
  */
140
144
  viewportTransform: fabric.iMatrix.concat(),
@@ -228,7 +232,7 @@
228
232
  * @private
229
233
  */
230
234
  _isRetinaScaling: function() {
231
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
235
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
232
236
  },
233
237
 
234
238
  /**
@@ -236,7 +240,7 @@
236
240
  * @return {Number} retinaScaling if applied, otherwise 1;
237
241
  */
238
242
  getRetinaScaling: function() {
239
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
243
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
240
244
  },
241
245
 
242
246
  /**
@@ -523,7 +527,7 @@
523
527
  }
524
528
 
525
529
  fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
526
-
530
+ this._originalCanvasStyle = this.lowerCanvasEl.style;
527
531
  if (this.interactive) {
528
532
  this._applyCanvasStyle(this.lowerCanvasEl);
529
533
  }
@@ -603,7 +607,7 @@
603
607
  }
604
608
  }
605
609
  if (this._isCurrentlyDrawing) {
606
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
610
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
607
611
  }
608
612
  this._initRetinaScaling();
609
613
  this.calcOffset();
@@ -670,8 +674,8 @@
670
674
  },
671
675
 
672
676
  /**
673
- * Sets viewport transform of this canvas instance
674
- * @param {Array} vpt the transform in the form of context.transform
677
+ * Sets viewport transformation of this canvas instance
678
+ * @param {Array} vpt a Canvas 2D API transform matrix
675
679
  * @return {fabric.Canvas} instance
676
680
  * @chainable true
677
681
  */
@@ -813,7 +817,7 @@
813
817
  * @chainable
814
818
  */
815
819
  clear: function () {
816
- this._objects.length = 0;
820
+ this.remove.apply(this, this.getObjects());
817
821
  this.backgroundImage = null;
818
822
  this.overlayImage = null;
819
823
  this.backgroundColor = '';
@@ -1159,7 +1163,7 @@
1159
1163
  version: fabric.version,
1160
1164
  objects: this._toObjects(methodName, propertiesToInclude),
1161
1165
  };
1162
- if (clipPath) {
1166
+ if (clipPath && !clipPath.excludeFromExport) {
1163
1167
  data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude);
1164
1168
  }
1165
1169
  extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
@@ -1202,24 +1206,32 @@
1202
1206
  * @private
1203
1207
  */
1204
1208
  __serializeBgOverlay: function(methodName, propertiesToInclude) {
1205
- var data = { }, bgImage = this.backgroundImage, overlay = this.overlayImage;
1209
+ var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage,
1210
+ bgColor = this.backgroundColor, overlayColor = this.overlayColor;
1206
1211
 
1207
- if (this.backgroundColor) {
1208
- data.background = this.backgroundColor.toObject
1209
- ? this.backgroundColor.toObject(propertiesToInclude)
1210
- : this.backgroundColor;
1212
+ if (bgColor && bgColor.toObject) {
1213
+ if (!bgColor.excludeFromExport) {
1214
+ data.background = bgColor.toObject(propertiesToInclude);
1215
+ }
1216
+ }
1217
+ else if (bgColor) {
1218
+ data.background = bgColor;
1211
1219
  }
1212
1220
 
1213
- if (this.overlayColor) {
1214
- data.overlay = this.overlayColor.toObject
1215
- ? this.overlayColor.toObject(propertiesToInclude)
1216
- : this.overlayColor;
1221
+ if (overlayColor && overlayColor.toObject) {
1222
+ if (!overlayColor.excludeFromExport) {
1223
+ data.overlay = overlayColor.toObject(propertiesToInclude);
1224
+ }
1225
+ }
1226
+ else if (overlayColor) {
1227
+ data.overlay = overlayColor;
1217
1228
  }
1229
+
1218
1230
  if (bgImage && !bgImage.excludeFromExport) {
1219
1231
  data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude);
1220
1232
  }
1221
- if (overlay && !overlay.excludeFromExport) {
1222
- data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude);
1233
+ if (overlayImage && !overlayImage.excludeFromExport) {
1234
+ data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude);
1223
1235
  }
1224
1236
 
1225
1237
  return data;
@@ -1750,6 +1762,10 @@
1750
1762
  }
1751
1763
  this.forEachObject(function(object) {
1752
1764
  object.dispose && object.dispose();
1765
+ // animation module is still optional
1766
+ if (fabric.runningAnimations) {
1767
+ fabric.runningAnimations.cancelByTarget(object);
1768
+ }
1753
1769
  });
1754
1770
  this._objects = [];
1755
1771
  if (this.backgroundImage && this.backgroundImage.dispose) {
@@ -1762,6 +1778,13 @@
1762
1778
  this.overlayImage = null;
1763
1779
  this._iTextInstances = null;
1764
1780
  this.contextContainer = null;
1781
+ // restore canvas style
1782
+ this.lowerCanvasEl.classList.remove('lower-canvas');
1783
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
1784
+ delete this._originalCanvasStyle;
1785
+ // restore canvas size to original size in case retina scaling was applied
1786
+ this.lowerCanvasEl.setAttribute('width', this.width);
1787
+ this.lowerCanvasEl.setAttribute('height', this.height);
1765
1788
  fabric.util.cleanUpJsdomNode(this.lowerCanvasEl);
1766
1789
  this.lowerCanvasEl = undefined;
1767
1790
  return this;
@@ -1,4 +1,114 @@
1
- (function() {
1
+ (function () {
2
+
3
+ var extend = fabric.util.object.extend,
4
+ clone = fabric.util.object.clone;
5
+
6
+ /**
7
+ * @typedef {Object} AnimationOptions
8
+ * @property {Function} [options.onChange] Callback; invoked on every value change
9
+ * @property {Function} [options.onComplete] Callback; invoked when value change is completed
10
+ * @property {Number} [options.startValue=0] Starting value
11
+ * @property {Number} [options.endValue=100] Ending value
12
+ * @property {Number} [options.byValue=100] Value to modify the property by
13
+ * @property {Function} [options.easing] Easing function
14
+ * @property {Number} [options.duration=500] Duration of change (in ms)
15
+ * @property {Function} [options.abort] Additional function with logic. If returns true, animation aborts.
16
+ *
17
+ * @typedef {() => void} CancelFunction
18
+ *
19
+ * @typedef {Object} AnimationCurrentState
20
+ * @property {number} currentValue value in range [`startValue`, `endValue`]
21
+ * @property {number} completionRate value in range [0, 1]
22
+ * @property {number} durationRate value in range [0, 1]
23
+ *
24
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
25
+ */
26
+
27
+ /**
28
+ * Array holding all running animations
29
+ * @memberof fabric
30
+ * @type {AnimationContext[]}
31
+ */
32
+ var RUNNING_ANIMATIONS = [];
33
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
34
+
35
+ /**
36
+ * cancel all running animations at the next requestAnimFrame
37
+ * @returns {AnimationContext[]}
38
+ */
39
+ cancelAll: function () {
40
+ var animations = this.splice(0);
41
+ animations.forEach(function (animation) {
42
+ animation.cancel();
43
+ });
44
+ return animations;
45
+ },
46
+
47
+ /**
48
+ * cancel all running animations attached to canvas at the next requestAnimFrame
49
+ * @param {fabric.Canvas} canvas
50
+ * @returns {AnimationContext[]}
51
+ */
52
+ cancelByCanvas: function (canvas) {
53
+ if (!canvas) {
54
+ return [];
55
+ }
56
+ var cancelled = this.filter(function (animation) {
57
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
58
+ });
59
+ cancelled.forEach(function (animation) {
60
+ animation.cancel();
61
+ });
62
+ return cancelled;
63
+ },
64
+
65
+ /**
66
+ * cancel all running animations for target at the next requestAnimFrame
67
+ * @param {*} target
68
+ * @returns {AnimationContext[]}
69
+ */
70
+ cancelByTarget: function (target) {
71
+ var cancelled = this.findAnimationsByTarget(target);
72
+ cancelled.forEach(function (animation) {
73
+ animation.cancel();
74
+ });
75
+ return cancelled;
76
+ },
77
+
78
+ /**
79
+ *
80
+ * @param {CancelFunction} cancelFunc the function returned by animate
81
+ * @returns {number}
82
+ */
83
+ findAnimationIndex: function (cancelFunc) {
84
+ return this.indexOf(this.findAnimation(cancelFunc));
85
+ },
86
+
87
+ /**
88
+ *
89
+ * @param {CancelFunction} cancelFunc the function returned by animate
90
+ * @returns {AnimationContext | undefined} animation's options object
91
+ */
92
+ findAnimation: function (cancelFunc) {
93
+ return this.find(function (animation) {
94
+ return animation.cancel === cancelFunc;
95
+ });
96
+ },
97
+
98
+ /**
99
+ *
100
+ * @param {*} target the object that is assigned to the target property of the animation context
101
+ * @returns {AnimationContext[]} array of animation options object associated with target
102
+ */
103
+ findAnimationsByTarget: function (target) {
104
+ if (!target) {
105
+ return [];
106
+ }
107
+ return this.filter(function (animation) {
108
+ return animation.target === target;
109
+ });
110
+ }
111
+ });
2
112
 
3
113
  function noop() {
4
114
  return false;
@@ -11,21 +121,30 @@
11
121
  /**
12
122
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
13
123
  * @memberOf fabric.util
14
- * @param {Object} [options] Animation options
15
- * @param {Function} [options.onChange] Callback; invoked on every value change
16
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
17
- * @param {Number} [options.startValue=0] Starting value
18
- * @param {Number} [options.endValue=100] Ending value
19
- * @param {Number} [options.byValue=100] Value to modify the property by
20
- * @param {Function} [options.easing] Easing function
21
- * @param {Number} [options.duration=500] Duration of change (in ms)
22
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
124
+ * @param {AnimationOptions} [options] Animation options
125
+ * @returns {CancelFunction} cancel function
23
126
  */
24
127
  function animate(options) {
128
+ options || (options = {});
129
+ var cancel = false,
130
+ context,
131
+ removeFromRegistry = function () {
132
+ var index = fabric.runningAnimations.indexOf(context);
133
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
134
+ };
25
135
 
26
- requestAnimFrame(function(timestamp) {
27
- options || (options = { });
136
+ context = extend(clone(options), {
137
+ cancel: function () {
138
+ cancel = true;
139
+ return removeFromRegistry();
140
+ },
141
+ currentValue: 'startValue' in options ? options.startValue : 0,
142
+ completionRate: 0,
143
+ durationRate: 0
144
+ });
145
+ fabric.runningAnimations.push(context);
28
146
 
147
+ requestAnimFrame(function(timestamp) {
29
148
  var start = timestamp || +new Date(),
30
149
  duration = options.duration || 500,
31
150
  finish = start + duration, time,
@@ -40,20 +159,31 @@
40
159
  options.onStart && options.onStart();
41
160
 
42
161
  (function tick(ticktime) {
43
- // TODO: move abort call after calculation
44
- // and pass (current,valuePerc, timePerc) as arguments
45
162
  time = ticktime || +new Date();
46
163
  var currentTime = time > finish ? duration : (time - start),
47
164
  timePerc = currentTime / duration,
48
165
  current = easing(currentTime, startValue, byValue, duration),
49
166
  valuePerc = Math.abs((current - startValue) / byValue);
50
- if (abort()) {
51
- onComplete(endValue, 1, 1);
167
+ // update context
168
+ context.currentValue = current;
169
+ context.completionRate = valuePerc;
170
+ context.durationRate = timePerc;
171
+ if (cancel) {
172
+ return;
173
+ }
174
+ if (abort(current, valuePerc, timePerc)) {
175
+ removeFromRegistry();
52
176
  return;
53
177
  }
54
178
  if (time > finish) {
179
+ // update context
180
+ context.currentValue = endValue;
181
+ context.completionRate = 1;
182
+ context.durationRate = 1;
183
+ // execute callbacks
55
184
  onChange(endValue, 1, 1);
56
185
  onComplete(endValue, 1, 1);
186
+ removeFromRegistry();
57
187
  return;
58
188
  }
59
189
  else {
@@ -62,6 +192,8 @@
62
192
  }
63
193
  })(start);
64
194
  });
195
+
196
+ return context.cancel;
65
197
  }
66
198
 
67
199
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -93,4 +225,5 @@
93
225
  fabric.util.animate = animate;
94
226
  fabric.util.requestAnimFrame = requestAnimFrame;
95
227
  fabric.util.cancelAnimFrame = cancelAnimFrame;
228
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
96
229
  })();
@@ -24,6 +24,7 @@
24
24
  * @param {Function} [options.onComplete] Callback; invoked when value change is completed
25
25
  * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
26
26
  * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
27
+ * @returns {Function} abort function
27
28
  */
28
29
  function animateColor(fromColor, toColor, duration, options) {
29
30
  var startColor = new fabric.Color(fromColor).getSource(),
@@ -32,7 +33,7 @@
32
33
  originalOnChange = options.onChange;
33
34
  options = options || {};
34
35
 
35
- fabric.util.animate(fabric.util.object.extend(options, {
36
+ return fabric.util.animate(fabric.util.object.extend(options, {
36
37
  duration: duration || 500,
37
38
  startValue: startColor,
38
39
  endValue: endColor,