diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index 0d2f6dc..1f18adc 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -53,6 +53,7 @@ interface IEditorOption { inactiveAlpha?: number // When the body content is out of focus, transparency. default: 0.6 historyMaxRecordCount: number // History (undo redo) maximum number of records. default: 100 printPixelRatio: number // Print the pixel ratio (larger values are clearer, but larger sizes). default: 3 + maskMargin: IMargin // Masking margins above the editor(for example: menu bar, bottom toolbar)。default: [0, 0, 0, 0] wordBreak: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD watermark?: IWatermark // Watermark{data:string; color?:string; opacity?:number; size?:number; font?:string;} control?: IControlOption // Control {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string;} diff --git a/docs/guide/option.md b/docs/guide/option.md index a9cf6d8..2da8288 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -53,6 +53,7 @@ interface IEditorOption { inactiveAlpha?: number // 正文内容失焦时透明度。默认值:0.6 historyMaxRecordCount: number // 历史(撤销重做)最大记录次数。默认:100次 printPixelRatio: number // 打印像素比率(值越大越清晰,但尺寸越大)。默认:3 + maskMargin: IMargin // 编辑器上的遮盖边距(如悬浮到编辑器上的菜单栏、底部工具栏)。默认:[0, 0, 0, 0] wordBreak: WordBreak // 单词与标点断行:BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认:BREAK_WORD watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;} control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string;} diff --git a/src/editor/core/cursor/Cursor.ts b/src/editor/core/cursor/Cursor.ts index 568bd17..59ed328 100644 --- a/src/editor/core/cursor/Cursor.ts +++ b/src/editor/core/cursor/Cursor.ts @@ -1,8 +1,11 @@ import { CURSOR_AGENT_HEIGHT } from '../../dataset/constant/Cursor' import { EDITOR_PREFIX } from '../../dataset/constant/Editor' +import { MoveDirection } from '../../dataset/enum/Observer' import { DeepRequired } from '../../interface/Common' import { ICursorOption } from '../../interface/Cursor' import { IEditorOption } from '../../interface/Editor' +import { IElementPosition } from '../../interface/Element' +import { findScrollContainer } from '../../utils' import { Draw } from '../draw/Draw' import { CanvasEvent } from '../event/CanvasEvent' import { Position } from '../position/Position' @@ -15,6 +18,11 @@ export type IDrawCursorOption = ICursorOption & { hitLineStartIndex?: number } +export interface IMoveCursorToVisibleOption { + direction: MoveDirection + cursorPosition: IElementPosition +} + export class Cursor { private readonly ANIMATION_CLASS = `${EDITOR_PREFIX}-cursor--animation` @@ -153,4 +161,53 @@ export class Cursor { this.cursorDom.style.display = 'none' this._clearBlinkTimeout() } + + public moveCursorToVisible(payload: IMoveCursorToVisibleOption) { + const { cursorPosition, direction } = payload + if (!cursorPosition || !direction) return + const { + pageNo, + coordinate: { leftTop, leftBottom } + } = cursorPosition + // 当前页面距离滚动容器顶部距离 + const prePageY = + pageNo * (this.draw.getHeight() + this.draw.getPageGap()) + + this.container.getBoundingClientRect().top + // 向上移动时:以顶部距离为准,向下移动时:以底部位置为准 + const isUp = direction === MoveDirection.UP + const x = leftBottom[0] + const y = isUp ? leftTop[1] + prePageY : leftBottom[1] + prePageY + // 查找滚动容器,如果是滚动容器是document,则限制范围为当前窗口 + const scrollContainer = findScrollContainer(this.container) + const rect = { + left: 0, + right: 0, + top: 0, + bottom: 0 + } + if (scrollContainer === document.documentElement) { + rect.right = window.innerWidth + rect.bottom = window.innerHeight + } else { + const { left, right, top, bottom } = + scrollContainer.getBoundingClientRect() + rect.left = left + rect.right = right + rect.top = top + rect.bottom = bottom + } + // 可视范围根据参数调整 + const { maskMargin } = this.options + rect.top += maskMargin[0] + rect.bottom -= maskMargin[2] + // 不在可视范围时,移动滚动条到合适位置 + if ( + !(x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) + ) { + const { scrollLeft, scrollTop } = scrollContainer + isUp + ? scrollContainer.scroll(scrollLeft, scrollTop - (rect.top - y)) + : scrollContainer.scroll(scrollLeft, scrollTop + y - rect.bottom) + } + } } diff --git a/src/editor/core/event/handlers/keydown.ts b/src/editor/core/event/handlers/keydown.ts index c2f2ca7..60de285 100644 --- a/src/editor/core/event/handlers/keydown.ts +++ b/src/editor/core/event/handlers/keydown.ts @@ -2,6 +2,7 @@ import { EditorZone } from '../../..' import { ZERO } from '../../../dataset/constant/Common' import { ElementType } from '../../../dataset/enum/Element' import { KeyMap } from '../../../dataset/enum/KeyMap' +import { MoveDirection } from '../../../dataset/enum/Observer' import { IElement, IElementPosition } from '../../../interface/Element' import { formatElementContext } from '../../../utils/element' import { isMod } from '../../../utils/hotkey' @@ -196,7 +197,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { } else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) { if (isReadonly) return let anchorPosition: IElementPosition = cursorPosition - const isUp = evt.key === KeyMap.Up + // 扩大选区时,判断移动光标点 if (evt.shiftKey) { if (startIndex === cursorPosition.index) { anchorPosition = positionList[endIndex] @@ -213,14 +214,15 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { rightTop: [curRightX] } } = anchorPosition - // 向上时在首行、向下时再最尾则忽略 + // 向上时在首行、向下时在尾行则忽略 + const isUp = evt.key === KeyMap.Up if ( (isUp && rowIndex === 0) || (!isUp && rowIndex === draw.getRowCount() - 1) ) { return } - // 查找下一行信息 + // 查找下一行位置列表 const probablePosition: IElementPosition[] = [] if (isUp) { let p = index - 1 @@ -269,10 +271,9 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { break } if (!nextIndex) return - const curIndex = nextIndex // shift则缩放选区 - let anchorStartIndex = curIndex - let anchorEndIndex = curIndex + let anchorStartIndex = nextIndex + let anchorEndIndex = nextIndex if (evt.shiftKey) { if (startIndex !== endIndex) { if (startIndex === cursorPosition.index) { @@ -300,6 +301,11 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) { isSubmitHistory: false, isCompute: false }) + // 将光标移动到可视范围内 + draw.getCursor().moveCursorToVisible({ + cursorPosition: positionList[isUp ? anchorStartIndex : anchorEndIndex], + direction: isUp ? MoveDirection.UP : MoveDirection.DOWN + }) } else if (isMod(evt) && evt.key === KeyMap.Z) { if (isReadonly) return historyManager.undo() diff --git a/src/editor/index.ts b/src/editor/index.ts index c05e6ce..e8a8492 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -148,6 +148,7 @@ export default class Editor { historyMaxRecordCount: 100, wordBreak: WordBreak.BREAK_WORD, printPixelRatio: 3, + maskMargin: [0, 0, 0, 0], ...options, header: headerOptions, footer: footerOptions, diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index 0f64663..713ad10 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -59,6 +59,7 @@ export interface IEditorOption { inactiveAlpha?: number historyMaxRecordCount?: number printPixelRatio?: number + maskMargin?: IMargin wordBreak?: WordBreak header?: IHeader footer?: IFooter diff --git a/src/editor/utils/index.ts b/src/editor/utils/index.ts index f005afd..d161fd1 100644 --- a/src/editor/utils/index.ts +++ b/src/editor/utils/index.ts @@ -219,3 +219,19 @@ export function convertStringToBase64(input: string) { const base64 = window.btoa(charArray.join('')) return base64 } + +export function findScrollContainer(element: HTMLElement) { + let parent = element.parentElement + while (parent) { + const style = window.getComputedStyle(parent) + const overflowY = style.getPropertyValue('overflow-y') + if ( + parent.scrollHeight > parent.clientHeight && + (overflowY === 'auto' || overflowY === 'scroll') + ) { + return parent + } + parent = parent.parentElement + } + return document.documentElement +} diff --git a/src/mock.ts b/src/mock.ts index 3f36bba..0202bf5 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -430,5 +430,6 @@ export const options: IEditorOption = { }, placeholder: { data: '请输入正文' - } + }, + maskMargin: [60, 0, 30, 0] // 菜单栏高度60,底部工具栏30为遮盖层 }