对象,属性和行为的缓存
怎么工作的?
当 Fabric 对象缓存(object caching)启用时,实际上你在画布上绘制的对象会先在另一个较小的离屏画布上进行预绘制,画布的大小与对象的像素尺寸相同。在 render
方法中,这个预绘制的画布会通过 drawImage
操作被复制到主画布上。
这意味着,在进行 drag
(拖拽)、rotate
(旋转)、skew
(倾斜)和 scale
(缩放)操作时,图像并不会重新绘制到画布上,而只是将其已缓存的图像绘制到画布上。
如何调整/自定义这一功能?
这个功能提供了 3 个属性,用来以不同的方式使用它:
/** * When `true`, object is cached on an additional canvas. * default to true * since 1.7.0 * @type Boolean * @default */objectCaching: objectCaching,
/** * When `true`, object properties are checked for cache invalidation. In some particular * situation you may want this to be disabled ( spray brush, very big pathgroups, groups) * or if your application does not allow you to modify properties for groups child you want * to disable it for groups. * default to false * since 1.7.0 * @type Boolean * @default */statefullCache: false,
/** * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled * too much and will be redrawn with correct details at the end of scaling. * this setting is performance and application dependant. * default to false * since 1.7.0 * @type Boolean * @default */noScaleCache: true,
/** * List of properties to consider when checking if cache needs refresh * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty * and refreshed at the next render * @type Array */cacheProperties: ( 'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' + ' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor').split(' '),
/** * When set to `true`, object's cache will be rerendered next render call. * @type Boolean * @default true */dirty: true,
/** * When return `true`, force the object to have its own cache, even if it is inside a group * it may be needed when your object behave in a particular way on the cache and always needs * its own isolated canvas to render correctly. * since 1.7.12 * @type function * @return false */ needsItsOwnCache: function() { return false; },
/** * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine. * @since 1.7.14 * @type Number * @default */fabric.perfLimitSizeTotal = 2097152;
/** * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000 * @since 1.7.14 * @type Number * @default */fabric.maxCacheSideLimit = 4096;
/** * Lowest pixel limit for cache canvases, set at 256PX * @since 1.7.14 * @type Number * @default */fabric.minCacheSideLimit = 256;
objectCaching
是主要属性,默认在浏览器中为 true
,在 Node 环境中为 false
,它在对象级别启用对象缓存(object caching)。
statefullCache
这个属性决定 Fabric 是否自动检查是否该重新绘制缓存的副本,或者开发者是否需要手动使其失效。此属性默认值为 false
。稍后会进一步介绍。
noScaleCache
默认值为 true
,禁用缩放操作时的缓存重新生成。启用它可以避免在进行大规模缩放时出现模糊效果。
cacheProperties
每次调用 Object.set(key, value)
时,key
会在这个属性数组中进行查找。如果找到,它会标记对象为需要重新渲染。设置 statefullCache
为 true
会避免每次渲染时比较所有键与旧值的副本。
dirty
是一个简单的标志,强制在下次渲染方法时重新生成缓存,并且在缓存重建后自动将其设置为 false
。
fabric.perfLimitSizeTotal
生成的缓存画布的最大像素面积。
fabric.maxCacheSideLimit
缓存画布最大的一边的像素尺寸。(大于 5000 的数值会在 IE 中导致崩溃)
fabric.minCacheSideLimit
缓存画布最小的一边的像素尺寸。(小于 256 的数值可能会禁用 GPU 合成,需要验证)
缓存画布示例
这是一个大于绘制对象的缓存画布示例(默认情况下,最小尺寸为 256x256):
这是一个最大尺寸的缓存画布示例,使用默认值(2 百万像素)。滚动查看更多内容:
性能提升有多少?是否会有问题?
这取决于你的项目需求。如果你只是绘制一些圆形、矩形和简单的多边形,可能性能提升不会很大。
如果你正在导入并显示大量复杂的 SVG 图形,那么你可能会从卡顿变得更加流畅。是否会有任何问题?你可能不喜欢 noScaleCache
特性,因此可以选择禁用它。
如果与我当前的项目不兼容,我应该更新吗?可以禁用缓存吗?
我建议更新到 1.7.0 版本,检查一切是否正常。如果不正常,报告任何视觉问题到 issue tracker,你仍然可以通过以下方式在你的项目中完全禁用该功能:
fabric.Object.prototype.objectCaching = false;
<Example1 client:idle hideCode canvasStyle={{ display: 'inline-block'}} /><Example2 client:idle hideCode canvasStyle={{ display: 'inline-block'}} />
你还可以观察到在 `noScaleCache` 设置为 `true` 或 `false` 时,缩放操作的差异。
在下面的画布中,左侧画布的 `noScaleCache` 设置为 `false`,这意味着在缩放变换期间对象不会被重新生成。如果你将对象缩放到原始大小的三倍以上,你会注意到模糊现象,直到你执行 `mouse up` 时,新的缓存副本才会修复这个问题。自己试试看:
<Example3 client:idle hideCode />
### 缓存何时更新为新版本?
Fabric 有一个硬编码的触发机制,用于在开发者没有容易插入代码的内置函数中更新缓存。这些情况包括:`缩放`、`输入文本`、`画布全局缩放`。在其他所有情况下,由于开发者正在改变对象的某些属性,因此应该确保在必要时通过将对象的 `dirty` 属性设置为 `true` 来强制更新缓存。因此,调用 `object.set('fill', 'red')` 时不需要其他操作。如果由于某些原因你没有使用 `set` 方法(例如,在文本对象的情况下,设置某些属性会触发昂贵的函数),你需要使用该标志。Fabric 还提供了一种方法,允许在渲染时检查属性的变化。这在大多数情况下不会很耗费性能,但我决定禁用它,因为在拥挤的情况下(如喷枪刷子或 1000+ 个 SVG 路径),会变得昂贵。
`Groups` 和 `PathGroups` 是通过以下方式进行处理的:当对象设置一个属性时,会检查该属性。如果它在 `cacheProperties` 数组中,对象和组都会被标记为 `dirty`。如果该属性在 `stateProperties` 数组中,则只有组会被标记为 `dirty`。
### 如何检查自定义子类与自定义属性的变化?
自定义子类化是我认为 Fabric 最强大的功能之一,且对象缓存就是考虑到这一点而构建的。因此,Fabric 中定义了一个数组 `cacheProperties`,该数组包含一系列属性,当 `statefullCache` 属性设置为 `true` 时(默认为 `false`),这些属性会在每次渲染时被检查。该数组的结构如下:
```js cacheProperties: ( 'fill stroke strokeWidth strokeDashArray width height' + ' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor' ).split(' '),
并且在不同的子类中会有更多的属性,例如 rect
类会增加 rx
和 ry
等等。属性是递归检查的,这意味着每次检测到变化时,会保存该属性的副本,并在下次渲染时进行深度比较。通常需要深度检查的属性包括渐变、图案、路径数组、strokeDash
。如果你的应用程序根本不使用某些属性,你可以从 cacheProperties
数组中移除它们,以加快检查速度,或者你可以添加自定义属性,确保它们对渲染有影响并进行检查。
常见问题
图片被裁剪出对象的边界框:是的。
有一个不可见的画布保持着对象的副本。这个画布的大小与对象的宽度/高度相同。如果宽度和高度对于我们试图显示的对象不正确,那么图片会被裁剪。唯一的解决方法是禁用该对象的缓存,或者修复该问题。
当前影响的用例:
- (自 1.7.7 起基本解决)有时可能看起来模糊
- (自 1.7.8 起基本解决)路径解析命令顺序错误(非常少见)
- (自 1.7.3 起基本解决)使用自定义字体的文本,其极大的 ascender/descender 值使得画布无法准确测量扩展。
- 组没有正确初始化(创建为空并且在使用
.add
方法后没有更新,请使用addWithUpdate
) - 缩放事件重置缩放并修改宽度/高度,禁用
noScaleCache
,将其设置为false