You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

react-quill集成quill-blot-formatter后编辑器滚动失效问题求助

解决Quill-Blot-Formatter导致React-Quill编辑器滚动失效的问题

问题概述

使用React-Quill编辑器,通过formats/image实现图片上传功能,为了添加图片缩放能力引入quill-blot-formatter并注册模块:Quill.register('modules/blotFormatter', BlotFormatter);。目前图片缩放功能正常,但鼠标处于缩放窗口内时,编辑器的滚动功能被禁用;鼠标移出缩放区域后,滚动恢复正常。推测是quill-blot-formatter的事件监听器阻止了鼠标事件冒泡到React-Quill的ql-editor元素。

参考代码

QuillEditor.js

import ReactQuill from 'react-quill';

const Quill = ReactQuill.Quill;
const Embed = Quill.import('blots/embed');
import BlotFormatter from 'quill-blot-formatter/dist/BlotFormatter';
import { CustomImage } from './CustomPlugins/CustomImage';

// 注册自定义图片Blot
Quill.register(CustomImage, true);
// 注册图片缩放模块
Quill.register('modules/blotFormatter', BlotFormatter);

<ReactQuill
  ref={editorRef}
  className={`w-full ${height} ${className} `}
  formats={formats}
  id={id}
  modules={modules(toolbarId)}
  theme="snow"
  value={state}
  onChange={handleChange}
/>

function modules(toolbarId) {
     return {
      blotFormatter: {
      specs: [CustomImageSpec], // 注册自定义规格
      align: {
        icons: alignmentStyles,
      },
    },
  };
}

CustomImageSpec.js

import DeleteIcon from '../../../assets/DeleteIcon.svg';
import { alignToolBar } from './helper';
import ImageSpec from 'quill-blot-formatter/dist/specs/ImageSpec';

// 自定义图片规格,为工具栏添加删除按钮
export class CustomImageSpec extends ImageSpec {
  img;
  deleteIcon;

  constructor(formatter) {
    super(formatter);
    this.img = null;
    this.deleteIcon = this.createDeleteIcon();
  }

  createDeleteIcon() {
    const spanElement = document.createElement('span');
    const imgElement = document.createElement('img');
    imgElement.src = DeleteIcon;
    imgElement.alt = 'test image';
    const clsList = ['blot-formatter__toolbar-button', 'blot-delete-icon'];
    spanElement.classList.add(...clsList);
    spanElement.appendChild(imgElement);
    spanElement.style.cssText =
      'display: flex; align-items:center; justify-content:center; width: 24px; height: 24px; cursor:pointer; user-select:none; padding: 2px';
    spanElement.addEventListener('click', this.onDeleteIconClick);
    return spanElement;
  }

  init() {
    this.formatter.quill.root.addEventListener('click', this.onClick);
    this.formatter.quill.root.addEventListener('scroll', (e) => {
       this.formatter.repositionOverlay();
    });
  }

  getTargetElement() {
    return this.img;
  }

  onDeleteIconClick = () => {
    // 处理删除按钮点击逻辑
    if (this.img) {
      this.img.remove();
      this.formatter.hide();
    }
  };

  onHide() {
    this.img = null;
  }

  onClick = (event) => {
    const el = event.target;
    if (!el || el.tagName !== 'IMG') {
      return;
    }
    this.img = el;
    this.formatter.show(this);
    alignToolBar(this.formatter);
    this.formatter.overlay
      ?.getElementsByClassName('blot-formatter__toolbar')[0]
      ?.appendChild(this.deleteIcon);
  };
}

CustomImage.js

import { Quill } from 'react-quill';

const BaseImage = Quill.import('formats/image');
const ATTRIBUTES = ['alt', 'height', 'width', 'style'];

export class CustomImage extends BaseImage {
  static formats(domNode) {
    return ATTRIBUTES.reduce(function (formats, attribute) {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute);
      }
      return formats;
    }, {});
  }
  format(name, value) {
    if (ATTRIBUTES?.indexOf(name) > -1) {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name);
      }
    } else {
      super.format(name, value);
    }
  }
}

// 注册自定义图片Blot
Quill.register(CustomImage, true);

解决方案

问题核心是quill-blot-formatter的遮罩层(overlay)默认会拦截鼠标滚轮事件,阻止其冒泡到编辑器容器。以下两种方法可解决该问题:

方法1:在CustomImageSpec中修改事件传递

在CustomImageSpec的init方法中,为遮罩层添加滚轮事件监听,允许事件继续冒泡:

init() {
  super.init(); // 必须调用父类初始化逻辑
  this.formatter.quill.root.addEventListener('click', this.onClick);
  this.formatter.quill.root.addEventListener('scroll', (e) => {
     this.formatter.repositionOverlay();
  });
  
  // 允许滚轮事件冒泡到编辑器
  this.formatter.overlay.addEventListener('wheel', (e) => {
    e.stopPropagation = false;
    e.cancelBubble = false;
  }, true);
}

方法2:通过模块配置修改遮罩层行为

在注册blotFormatter模块时,直接配置遮罩层的事件处理:

function modules(toolbarId) {
  return {
    blotFormatter: {
      specs: [CustomImageSpec],
      align: {
        icons: alignmentStyles,
      },
      overlay: {
        onCreated: (overlay) => {
          // 覆盖遮罩层的滚轮事件拦截逻辑
          overlay.addEventListener('wheel', (e) => {
            e.stopImmediatePropagation();
            const newEvent = new WheelEvent(e.type, e);
            this.formatter.quill.root.dispatchEvent(newEvent);
          }, true);
        }
      }
    },
  };
}

补充处理

如果上述方法无效,说明quill-blot-formatter内部调用了e.preventDefault(),可通过以下方式强制解除拦截:

this.formatter.overlay.addEventListener('wheel', (e) => {
  e.stopImmediatePropagation();
  e.preventDefault = () => {}; // 覆盖默认的阻止默认行为
}, true);

通过以上修改,鼠标在图片缩放窗口内时,滚轮事件将正常传递到React-Quill编辑器,恢复滚动功能。


内容的提问来源于stack exchange,提问作者Muthukumaran Ganapathy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:43:12