基于fabric自定义svg光标

基于fabric自定义svg光标

技术博客 admin 149 浏览

如何自定义光标?

fabric的光标实际上是和浏览器默认一致的,也就是说,浏览器有啥就有啥,没有啥就没有啥
但是我们很多时候默认的浏览器光标并不能满足需求
例如,我们做个魔术橡皮的功能,光标要求是个棒棒

这种情况我们就只能想办法自定义光标了
但是搜了一遍都找不到有相关的办法,所以只能自己实现了

如何绘制自定义的光标

因为我们是在fabric上去进行自定义光标,我们这里有两个绘制自定义光标的方案:

  1. 在canvas上进行绘制,如果这样的话我们需要先导入svg,然后将其作为一个单独图层进行处理,在鼠标事件进行变化时进行计算
  2. 在dom上进行绘制,好处在于不需要将处理耦合进画布中,并且经过实测性能表现会更好一些

如果在dom上绘制光标

定义dom

如果我们需要绘制光标,那么我们需要注意以下几点:

  1. 肯定需要先定义一个div作为光标挂载的位置
  2. 并且这个dom的定位必须为绝对定位以方便计算
  3. 光标的原本的pointer事件必须禁止
  4. 初始的光标显示默认定义为隐藏
  5. 并且因为是光标,我们必须要注意到这个dom的层级,需要设置为最高

所以具体的实现处理如下:

typescript
复制代码
const cursorDiv = document.createElement('div') cursorDiv.style.position = 'absolute' cursorDiv.style.pointerEvents = 'none' cursorDiv.style.display = 'none' cursorDiv.style.zIndex = '9999'

将自定义的 SVG 加载到 div 元素中

这里需要将我们需要的自定义样式加载到div里面,我这里是为了方便直接将svg写成一个字符串

typescript
复制代码
cursorDiv.innerHTML = ` <svg t="1718954739568" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4872" width="24" height="24" > <path d="M648.490667 424.277333a110.933333 110.933333 0 0 1-33.706667-66.133333l-17.194667-131.541333-116.522666 63.402666a110.933333 110.933333 0 0 1-73.344 11.605334L277.333333 277.333333l24.32 130.389334a110.933333 110.933333 0 0 1-11.648 73.386666l-63.402666 116.48 131.541333 17.194667a110.933333 110.933333 0 0 1 66.133333 33.706667l91.221334 96.298666 57.002666-119.765333a110.933333 110.933333 0 0 1 52.48-52.522667l119.808-57.002666-96.298666-91.221334z m1.066666 237.397334l-94.421333 198.4a25.6 25.6 0 0 1-41.685333 6.613333l-151.125334-159.530667a25.6 25.6 0 0 0-15.274666-7.765333l-217.856-28.501333a25.6 25.6 0 0 1-19.2-37.589334l105.045333-193.024a25.6 25.6 0 0 0 2.688-16.896L177.493333 207.36a25.6 25.6 0 0 1 29.866667-29.866667l215.978667 40.234667a25.6 25.6 0 0 0 16.938666-2.688l192.981334-104.96a25.6 25.6 0 0 1 37.632 19.114667l28.501333 217.898666a25.6 25.6 0 0 0 7.765333 15.232l159.530667 151.125334a25.6 25.6 0 0 1-6.613333 41.685333l-198.4 94.421333a25.6 25.6 0 0 0-12.117334 12.117334z m34.005334 82.218666l60.330666-60.330666 180.992 180.992-60.330666 60.373333-180.992-181.034667z" p-id="4873" > </path> </svg>`

然后我们需要将div的元素样式设置为光标的样式

typescript
复制代码
cursorDiv.style.cursor = 'url("data:image/svg+xml;base64,' + btoa(cursorDiv.innerHTML) + '"), auto' cursorDiv.id = 'cursorDiv'

将这个div添加到body中去

typescript
复制代码
document.body.appendChild(cursorDiv)

如何限制自定义光标只在某个区域内生效?

因为我们这里对光标的要求只需要在图片中的移动为棒棒,在图片之外还是正常的光标 所以我们就必须在进行光标的绘制和处理的时候进行判断当前的光标移动是否在图片内

typescript
复制代码
judgeCursorIsInImg(pointer) { // 获取Fabric.js画布上的所有对象 const objects = this.owlCanvas.getObjects() objects.forEach((obj) => { if (obj.type === 'image') { // @ts-ignore const isHovering = obj.containsPoint({ x: pointer.x, y: pointer.y }) if (isHovering) { this.customCursor.style.display = 'block' this.canvas.defaultCursor = 'none' this.canvas.upperCanvasEl.style.cursor = 'none' } else { this.customCursor.style.display = 'none' this.canvas.defaultCursor = 'default' this.canvas.upperCanvasEl.style.cursor = 'default' } } else { this.customCursor.style.display = 'none' } }) }

其中的

typescript
复制代码
this.canvas.defaultCursor = 'none' this.canvas.upperCanvasEl.style.cursor = 'none'

是为了去除fabric原本默认的鼠标样式

如何计算当前光标在canvas画布上的真实位置

众所周知,在 fabric 的 canvas 画布上鼠标移动事件获得的坐标和我们实际在视窗中绘制的坐标是不一样的,所以我们需要对当前canvas画布在视窗中所在位置的偏移量进行一个计算才能让我们的自定义坐标刚好在原本的坐标位置上

typescript
复制代码
this.canvasOffsetX = canvasRect.left + window.scrollX - 12 this.canvasOffsetY = canvasRect.top + window.scrollY - 12

而在鼠标事件触发需要更新坐标位置时我们需要计算的方式也是一样

typescript
复制代码
updateCustomCursor(x, y) { if (this.customCursor) { this.customCursor.style.left = x + this.canvasOffsetX + 'px' this.customCursor.style.top = y + this.canvasOffsetY + 'px' } }

移除移入及销毁时光标的处理

在鼠标移入移出我们需要自定义光标区域的时候我们需要对当前的光标进行一个判断和处理

typescript
复制代码
onMouseOver(event: fabric.IEvent) { const pointer = this.canvas.getPointer(event.e) this.judgeCursorIsInImg(pointer) } onMouseOut(event: fabric.IEvent) { const pointer = this.canvas.getPointer(event.e) this.judgeCursorIsInImg(pointer) }

记得在销毁这个示例的时候要移除掉这个dom

typescript
复制代码
destroyed() { document.getElementById('cursorDiv')?.remove() this.owlCanvas.defaultCursor = 'default' this.owlCanvas.upperCanvasEl.style.cursor = 'default' }

整体实现

下面是这个类的完整实现

typescript
复制代码
class CustomCursor { private customCursor private canvasOffsetX: number = 0 private canvasOffsetY: number = 0 override initialize() { // 获取 Canvas 元素的相对于 document 的偏移量 const canvasRect = this.owlCanvas.getElement().getBoundingClientRect() this.canvasOffsetX = canvasRect.left + window.scrollX - 12 this.canvasOffsetY = canvasRect.top + window.scrollY - 12 this.createCustomCursor() } createCustomCursor() { const cursorDiv = document.createElement('div') cursorDiv.style.position = 'absolute' cursorDiv.style.pointerEvents = 'none' cursorDiv.style.display = 'none' cursorDiv.style.zIndex = '9999' // 将自定义的 SVG 加载到 div 元素中 cursorDiv.innerHTML = ` <svg t="1718954739568" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4872" width="24" height="24" > <path d="M648.490667 424.277333a110.933333 110.933333 0 0 1-33.706667-66.133333l-17.194667-131.541333-116.522666 63.402666a110.933333 110.933333 0 0 1-73.344 11.605334L277.333333 277.333333l24.32 130.389334a110.933333 110.933333 0 0 1-11.648 73.386666l-63.402666 116.48 131.541333 17.194667a110.933333 110.933333 0 0 1 66.133333 33.706667l91.221334 96.298666 57.002666-119.765333a110.933333 110.933333 0 0 1 52.48-52.522667l119.808-57.002666-96.298666-91.221334z m1.066666 237.397334l-94.421333 198.4a25.6 25.6 0 0 1-41.685333 6.613333l-151.125334-159.530667a25.6 25.6 0 0 0-15.274666-7.765333l-217.856-28.501333a25.6 25.6 0 0 1-19.2-37.589334l105.045333-193.024a25.6 25.6 0 0 0 2.688-16.896L177.493333 207.36a25.6 25.6 0 0 1 29.866667-29.866667l215.978667 40.234667a25.6 25.6 0 0 0 16.938666-2.688l192.981334-104.96a25.6 25.6 0 0 1 37.632 19.114667l28.501333 217.898666a25.6 25.6 0 0 0 7.765333 15.232l159.530667 151.125334a25.6 25.6 0 0 1-6.613333 41.685333l-198.4 94.421333a25.6 25.6 0 0 0-12.117334 12.117334z m34.005334 82.218666l60.330666-60.330666 180.992 180.992-60.330666 60.373333-180.992-181.034667z" p-id="4873" > </path> </svg>` // 设置 div 元素的样式为光标样式 cursorDiv.style.cursor = 'url("data:image/svg+xml;base64,' + btoa(cursorDiv.innerHTML) + '"), auto' cursorDiv.id = 'cursorDiv' document.body.appendChild(cursorDiv) this.customCursor = document.getElementById('cursorDiv') } judgeCursorIsInImg(pointer) { // 获取Fabric.js画布上的所有对象 const objects = this.owlCanvas.getObjects() objects.forEach((obj) => { if (obj.type === 'image') { // @ts-ignore const isHovering = obj.containsPoint({ x: pointer.x, y: pointer.y }) if (isHovering) { this.customCursor.style.display = 'block' this.canvas.defaultCursor = 'none' this.canvas.upperCanvasEl.style.cursor = 'none' } else { this.customCursor.style.display = 'none' this.canvas.defaultCursor = 'default' this.canvas.upperCanvasEl.style.cursor = 'default' } } else { this.customCursor.style.display = 'none' } }) } override onMousemove(event: fabric.IEvent) { if (!this.customCursor) return const pointer = this.owlCanvas.getPointer(event.e) this.judgeCursorIsInImg(pointer) this.updateCustomCursor(pointer.x, pointer.y) } override onMouseup() { // this.customCursor && (this.customCursor.style.display = 'none') } onMouseOver(event: fabric.IEvent) { const pointer = this.canvas.getPointer(event.e) this.judgeCursorIsInImg(pointer) } onMouseOut(event: fabric.IEvent) { const pointer = this.canvas.getPointer(event.e) this.judgeCursorIsInImg(pointer) } updateCustomCursor(x, y) { if (this.customCursor) { this.customCursor.style.left = x + this.canvasOffsetX + 'px' this.customCursor.style.top = y + this.canvasOffsetY + 'px' } } override onMousedown(event: fabric.IEvent) { } destroyed() { document.getElementById('cursorDiv')?.remove() this.owlCanvas.defaultCursor = 'default' this.owlCanvas.upperCanvasEl.style.cursor = 'default' } } export default CustomCursor

实现效果

源文:基于fabric自定义svg光标

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!