generated at
scrapbox-position
Next version:
hr
指定した座標にあるscrpabox editor上の諸々の情報を取得する函数群

2020-12-29
08:36:23 境界の取得に失敗したら、何に失敗したのか情報を返すようにした。
2020-12-26
03:02:09 行の高さの取得方法を変えた
Window.getComputedStyle()を使わない方法に切り替えた
2020-12-25
破壊的変更: 判定計算で切り捨て処理を行わないようにした
2020-12-24 04:10:44
行頭行末文字より外側の座標であっても、文字のDOMを返すようにした
LF HEAD の情報はつける

改善案
座標の計算に2分探索を用いる
多少速くなる
頻繁に getCharBorder() を呼び出すコードを書くようになったら検討する

内容物
getCharBorder({x,y})
指定した座標に一番近い文字境界を取得する
cursorや選択範囲の端など、ほぼ文字境界に位置する座標に対して用いる
引数
x : ページの可視領域の左上を原点とした座標系でのx成分
y : ページの可視領域の左上を原点とした座標系でのy成分
返り値
ts
type Return = { left?: HTMLSpanElement; //境界の左側の文字 right?: HTMLSpanElement; //境界の右側の文字 line?: HTMLDivElement; // 文字がいる行 } |{ line: HTMLDivElement; message: 'No char found'; } | { message: 'No line found.' | 'No border matched.'; };
行頭や行末など、片側の文字がないときはundefinedが入る
境界が見つからなかったときはすべてundefinedになる
script.js
import {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 の値を用いる
script.js
return {line, ...border}; }

getChar({x,y})
指定した座標上にある文字のDOMを取得する
引数
x : ページの可視領域の左上を原点とした座標系でのx成分
y : ページの可視領域の左上を原点とした座標系でのy成分
返り値
ts
type Return = { // 文字が見つかったとき type: 'CHAR'; line: HTMLDivElement; char: HTMLSpanElement; } | { // 行末文字より右側だったとき type: 'LF'; line: HTMLDivElement; char: HTMLSpanElement; // 行末文字 } | { // 行末文字より左側だった場合 type: 'HEAD'; line: HTMLDivElement; char: HTMLSpanElement; // 行頭文字 } | { // 行が見つからなかった場合 type: 'NOLINE'; };
script.js
export 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.js
export function getLine({y}) { return [...scrapboxDOM.lines.children].find(line =>{ const rect = line.getBoundingClientRect(); return rect.top <= y && y < rect.bottom; }); }

#2021-01-27 20:19:40
#2021-01-24 00:13:53
#2021-01-10 10:37:27
#2020-12-29 08:40:33
#2020-12-28 10:17:45
#2020-12-26 02:55:15
#2020-12-25 12:00:42
#2020-12-24 04:12:49
#2020-12-11 06:06:29