Skip to content

Commit

Permalink
feat: #1005 增加滚动到具体位置的api,增加滚动行为控制的参数,修复预览区滚动没有触发编辑区滚动的bug
Browse files Browse the repository at this point in the history
  • Loading branch information
sunsonliu committed Dec 22, 2024
1 parent 99f6bae commit 7b7f7ad
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 38 deletions.
14 changes: 12 additions & 2 deletions examples/api.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ <h2 class="one-api__name">getPreviewer()</h2>
</div>

<div class="one-api">
<h2 class="one-api__name">Previewer.scrollToId(id:string)</h2>
<p class="one-api__desc">滚动到对应id的元素位置<br>id 可以为带#号hash,也可以是id值</p>
<h2 class="one-api__name">Previewer.scrollToId(id:string, behavior:{'smooth'|'instant'|'auto'})</h2>
<p class="one-api__desc">滚动到对应id的元素位置<br>id 可以为带#号hash,也可以是id值<br>behavior: smooth(默认) 平滑滚动;instant 立即滚动;auto 跟随浏览器默认行为</p>
<div class="one-api__try">
<textarea id="setMarkdown" placeholder="输入内容">
// cherryObj.previewer.scrollToId('#gettoc'); 两种写法都可以
Expand All @@ -219,6 +219,16 @@ <h2 class="one-api__name">Previewer.scrollToId(id:string)</h2>
</div>
</div>

<div class="one-api">
<h2 class="one-api__name">Previewer.scrollToTop(scrollTop:number, behavior:{'auto'|'smooth'|'instant'})</h2>
<p class="one-api__desc">滚动到对应位置<br>scrollTop 滚动距离<br>behavior: auto(默认) 跟随浏览器默认行为;smooth 平滑滚动;instant 立即滚动</p>
<div class="one-api__try">
<textarea id="setMarkdown" placeholder="输入内容">
cherryObj.previewer.scrollToTop(3000);</textarea>
<a class="one-api__btn" onclick="dealClick(this, event)">试一试</a>
</div>
</div>

<div class="one-api">
<h2 class="one-api__name">setLocale(locale:string)</h2>
<p class="one-api__desc">修改语言<br>系统默认支持:zh_CN | en_US | ru_RU</p>
Expand Down
71 changes: 35 additions & 36 deletions src/Previewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import vDH from 'virtual-dom/h';
import vDDiff from 'virtual-dom/diff';
import vDPatch from 'virtual-dom/patch';
import MyersDiff from './utils/myersDiff';
import { getBlockTopAndHeightWithMargin, elementsFromPoint } from './utils/dom';
import { getBlockTopAndHeightWithMargin } from './utils/dom';
import Logger from './Logger';
// import locale from './utils/locale';
import { addEvent, removeEvent } from './utils/event';
Expand Down Expand Up @@ -416,51 +416,34 @@ export default class Previewer {
this.editor.scrollToLineNum(null);
return;
}
// 获取预览容器基准坐标
const basePoint = domContainer.getBoundingClientRect();
// 观察点坐标,取容器中轴线
const watchPoint = {
x: basePoint.left + basePoint.width / 2,
y: basePoint.top + 1,
};
// 获取观察点处的DOM
const targetElements = elementsFromPoint(watchPoint.x, watchPoint.y);
const domPosition = domContainer.getBoundingClientRect();
let targetElement;
for (let i = 0; i < targetElements.length; i++) {
if (domContainer.contains(targetElements[i])) {
targetElement = targetElements[i];
let lines = 0;
const elements = domContainer.children;
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.getBoundingClientRect().top < domPosition.top) {
targetElement = element;
const currentLines = element.getAttribute('data-lines') ?? 0;
lines += +currentLines;
} else {
break;
}
}
if (!targetElement || targetElement === domContainer) {
return;
}
// 获取观察点处最近的markdown元素
let mdElement = targetElement.closest('[data-sign]');
// 由于新增脚注,内部容器也有可能存在data-sign,所以需要循环往父级找
while (mdElement && mdElement.parentElement && mdElement.parentElement !== domContainer) {
mdElement = mdElement.parentElement.closest('[data-sign]');
}
if (!mdElement) {
if (!targetElement) {
this.editor.scrollToLineNum(0, 0, 1);
return;
}
// 计算当前焦点容器的所在行数
let lines = 0;
let element = mdElement;
while (element) {
lines += +element.getAttribute('data-lines');
element = element.previousElementSibling; // 取上一个兄弟节点,直到为null
}
// markdown元素存在margin,getBoundingRect不能获取到margin
const mdElementStyle = getComputedStyle(mdElement);
const mdElementStyle = getComputedStyle(targetElement);
const marginTop = parseFloat(mdElementStyle.marginTop);
const marginBottom = parseFloat(mdElementStyle.marginBottom);
// markdown元素基于当前页面的矩形模型
const mdRect = mdElement.getBoundingClientRect();
const mdRect = targetElement.getBoundingClientRect();
const mdActualHeight = mdRect.height + marginTop + marginBottom;
// (mdRect.y - marginTop)为顶部触达区域,basePoint.y为预览区域的顶部,故可视范围应减去预览区域的偏移
const mdOffsetTop = mdRect.y - marginTop - basePoint.y;
const lineNum = +mdElement.getAttribute('data-lines'); // 当前markdown元素所占行数
const mdOffsetTop = mdRect.y - marginTop - domPosition.y;
const lineNum = +targetElement.getAttribute('data-lines'); // 当前markdown元素所占行数
const percent = (100 * Math.abs(mdOffsetTop)) / mdActualHeight / 100;
// console.log('destLine:', lines, percent,
// mdRect.height + marginTop + marginBottom, mdOffsetTop, mdElement);
Expand Down Expand Up @@ -940,12 +923,28 @@ export default class Previewer {
this.highlightLine(lineNum);
}

/**
* 滚动到对应位置
* @param {number} scrollTop 元素的id属性值
* @param {'auto'|'smooth'|'instant'} behavior 滚动方式
*/
scrollToTop(scrollTop, behavior = 'auto') {
const previewDom = this.getDomContainer();
const scrollDom = this.getDomCanScroll(previewDom);
scrollDom.scrollTo({
top: scrollTop,
left: 0,
behavior,
});
}

/**
* 滚动到对应id的位置,实现锚点滚动能力
* @param {string} id 元素的id属性值
* @param {'smooth'|'instant'|'auto'} behavior 滚动方式
* @return {boolean} 是否有对应id的元素并执行滚动
*/
scrollToId(id) {
scrollToId(id, behavior = 'smooth') {
const previewDom = this.getDomContainer();
const scrollDom = this.getDomCanScroll(previewDom);
let $id = id.replace(/^\s*#/, '').trim();
Expand All @@ -963,7 +962,7 @@ export default class Previewer {
scrollDom.scrollTo({
top: scrollTop,
left: 0,
behavior: 'smooth',
behavior,
});
}

Expand Down

0 comments on commit 7b7f7ad

Please sign in to comment.