scrapbox-position
Next version:
指定した座標にあるscrpabox editor上の諸々の情報を取得する函数群
2020-12-29
08:36:23 境界の取得に失敗したら、何に失敗したのか情報を返すようにした。
2020-12-26
03:02:09 行の高さの取得方法を変えた
2020-12-25
破壊的変更: 判定計算で切り捨て処理を行わないようにした
2020-12-24 04:10:44
行頭行末文字より外側の座標であっても、文字のDOMを返すようにした
LF
や HEAD
の情報はつける
改善案
多少速くなる
頻繁に getCharBorder()
を呼び出すコードを書くようになったら検討する
内容物
getCharBorder({x,y})
指定した座標に一番近い文字境界を取得する
cursorや選択範囲の端など、ほぼ文字境界に位置する座標に対して用いる
引数
x
: ページの可視領域の左上を原点とした座標系でのx成分
y
: ページの可視領域の左上を原点とした座標系でのy成分
返り値
tstype Return = {
left?: HTMLSpanElement; //境界の左側の文字
right?: HTMLSpanElement; //境界の右側の文字
line?: HTMLDivElement; // 文字がいる行
} |{
line: HTMLDivElement;
message: 'No char found';
} | {
message: 'No line found.' | 'No border matched.';
};
行頭や行末など、片側の文字がないときはundefinedが入る
境界が見つからなかったときはすべてundefinedになる
script.jsimport {scrapboxDOM} from '/api/code/takker/scrapbox-dom-accessor/script.js';
export function getCharBorder({x, y}) {
const line = getLine({y});
if (!line) return {message: 'No line found.'};
const chars = [...line.querySelectorAll('span[class^="c-"]')];
const {top,left,height} = line.getBoundingClientRect();
↓ここおかしい
clientHeight
では折返しも含めた行全体の高さが取得されてしまう
これでは折返し数なんて計算できないはずだが……?
でも計算できているんだよな?変だな。
y - top
が負数になった場合の処理がない
getLine()
で行を検索した時点で、 y > top
なのは確定している、ということだろうか?
しかし行と行の間に僅かな隙間があったり、タイトル行より前の位置を指定した場合はどうしようもない
この辺書き直したほうが良さそう
script.js // 折返しされた行で何行目から文字を検索するのか
const lineHeight = line.clientHeight;
const breakNum = Math.floor((y - top) / lineHeight);
// 指定した座標を含む表示上の行内に含まれる文字のリスト
const charsInTargetVisibleLine = height > lineHeight ?
chars.filter(char => {
const charTop = char.getBoundingClientRect().top;
return top + breakNum * lineHeight <= charTop
&& charTop < top + (breakNum + 1) * lineHeight;
}) :
chars;
if (charsInTargetVisibleLine.length === 0) return {line, message: 'No char found'};
// 文字境界のリストを作る
const borders = [...charsInTargetVisibleLine
.map((char, i, charList) => {
return {left: i === 0 ? undefined : charList[i - 1], right: char};
}), {left: charsInTargetVisibleLine.pop(), right: undefined}];
// x成分から文字境界を検索する
//console.log({borders});
const border = borders
.find(({left: lChar, right: rChar}) => {
const lRect = lChar?.getBoundingClientRect?.() ?? rChar.getBoundingClientRect();
const rRect = rChar?.getBoundingClientRect?.() ?? lChar.getBoundingClientRect();
const left = (lChar ? lRect.left : lRect.left - lRect.width) + lRect.width / 2;
const right = (rChar ? rRect.left : rRect.right) + rRect.width / 2;
//console.log({left, right});
return left <= x && x < right;
}) ?? {message: 'No border matched.'};
境界の判定方法
それぞれの文字の中間を判定領域の端とする
行頭や行末など、どちらかの文字がない場合は、一方の文字の width
の値を用いる
getChar({x,y})
指定した座標上にある文字のDOMを取得する
引数
x
: ページの可視領域の左上を原点とした座標系でのx成分
y
: ページの可視領域の左上を原点とした座標系でのy成分
返り値
tstype Return = {
// 文字が見つかったとき
type: 'CHAR';
line: HTMLDivElement;
char: HTMLSpanElement;
} | {
// 行末文字より右側だったとき
type: 'LF';
line: HTMLDivElement;
char: HTMLSpanElement; // 行末文字
} | {
// 行末文字より左側だった場合
type: 'HEAD';
line: HTMLDivElement;
char: HTMLSpanElement; // 行頭文字
} | {
// 行が見つからなかった場合
type: 'NOLINE';
};
script.jsexport function getChar({x, y}) {
const line = getLine({y});
if (!line) return {type: 'NOLINE'};
const chars = [...line.querySelectorAll('span[class^="c-"]')];
const lineHeight = scrapboxDOM.lines.lastElementChild.clientHeight;
script.js const {top,left,height} = line.getBoundingClientRect();
// 折返しされた行で何行目から文字を検索するのか
const breakNum = Math.floor((y - top) / lineHeight);
// 指定した座標を含む表示上の行内に含まれる文字のリスト
const charsInTargetVisibleLine = height > lineHeight ?
chars.filter(char => {
const charTop = char.getBoundingClientRect().top;
return top + breakNum * lineHeight <= charTop
&& charTop < top + (breakNum + 1) * lineHeight;
}) :
chars;
// 先に範囲外かどうかを調べる
if (charsInTargetVisibleLine[0].getBoundingClientRect().left > x) {
return {type: 'HEAD', line, char: chars[0]};
}
if (charsInTargetVisibleLine[charsInTargetVisibleLine.length - 1]
.getBoundingClientRect().right < x) {
return {type: 'LF', line, char: chars.pop()};
}
// x成分から文字を検索する
const char = charsInTargetVisibleLine
.find(char => {
const rect = char.getBoundingClientRect();
return rect.left <= x && x < rect.right;
↑小数点が入ると判定がずれてしまうので全て切り捨てる
一旦やめてみた
script.js });
if (!char) return {type: 'LF', line, char: chars.pop()};
/*const rects = chars.map(char => {
const rect = char.getBoundingClientRect();
return {x: rect.x,x2: rect.right};
});*/
//console.log({x,y,char,rects,line});
return {type:'DOM', line, char};
}
getCharFromLine({x, line})
指定した座標と行にある文字のDOMを取得する
実装しない
折返し行の何行目の文字を取得するかを決めるために、y成分を渡して貰う必要がある
するとそのy成分が line
の領域内に含まれるかどうかcheckしなければならず、複雑になる
getLine({y})
指定した座標上にある行のDOMを取得する
引数
y
: ページの可視領域の左上を原点とした座標系でのy成分
返り値
HTMLDivElement | undefined
行がなかったらundefinedになる
script.jsexport function getLine({y}) {
return [...scrapboxDOM.lines.children].find(line =>{
const rect = line.getBoundingClientRect();
return rect.top <= y && y < rect.bottom;
});
}