2017年4月10日月曜日

テキストエリア内のカーソル位置に追従して水平線を移動する

4Kモニタのように、高解像度で精密な映像を表示するモニタを使っている時はテキストエディタ画面上で点滅しているテキストカーソルも小さめに表示されるため、たまに見失うことがあります。
カーソルがどこにあるのか分かり易くするために、カーソルに水平線を追従させるサンプルを作成してみました。




追従している様子



ソース
<html>
<head>
<title>自動インデント</title>
<style type="text/css">
.max{width:100%;height:100%}
.bc{border-collapse:collapse}
.of-hidden{overflow:hidden}
</style>
</head>
<body class="of-hidden">
<table class="bc max">
<tr><td><textarea id=ta class=max wrap=off style="line-height:16px"></textarea></td></tr>
<tr height=1><td>行:<input id=inp行> 列:<input id=inp列></td></tr>
</table>
<hr id=hr style="position:absolute">
</body>
<script>
resizeTo(400,400)
ta.onfocus=ta.onclick=ta.onkeyup=ta.onkeydown=function(){
var obj=getテキストカーソル位置(this), str=this.value.slice(0,obj.start), arr=str.split('\n')
inp行.value = arr.length
inp列.value = arr[arr.length-1].length + 1
カーソル追従()
}
sumOffset=function(elem, p){
var n=elem[p], PA='parentNode', n0
while(elem[PA]){ n0=(elem=elem[PA])[p], n += isFinite(n0) ? n0 : 0 }
return n
}
カーソル追従=function(){
var lh=getLineHeight(ta), s=hr.style
s.top = sumOffset(ta,'offsetTop') + lh * inp行.value - ta.scrollTop
s.left = sumOffset(ta,'offsetLeft')
s.width = ta.clientWidth
}
getLineHeight=function(ta){
var c=document.body.insertBefore(ta.cloneNode(true)), h='scrollHeight', v=c.value='', h0=c[h], v=c.value='A\n', h1=c[h]
return c.removeNode(true), h1-h0
}
ta.onkeydown=function(){
var obj={9:タブキー操作, 13:エンターキー操作}, kc=event.keyCode
return obj[kc] ? obj[kc](this) : 0
}
エンターキー操作=function(elem){
var obj=getテキストカーソル位置(elem), res=elem.value.slice(0,obj.start).match(/(\r?\n?)([\t ]*)[^\r\n]*$/), rng=document.selection.createRange()
rng.text = (res[1] || '\r\n')+res[2]
rng.select()
return false
}
タブキー操作=function(elem){
event.cancelBubble = true
var obj=getテキストカーソル位置(elem), rng=document.selection.createRange(), v=elem.value, L=v.length, str=v.slice(obj.start, obj.end)
if(event.shiftKey){
if(str){
rng.text = str.replace(/\n\t|\n {4}/g,'\n').replace(/^\t|^ {4}/g,'')
}else if(obj.end < L){
var 文字数=Math.min(4,L-obj.end), reg=/^\t|^ {1,4}/, res
rng.moveEnd('character', 文字数)
if(res=rng.text.match(reg)){}else{ return false }
rng.text = rng.text.replace(reg,'')
rng.moveEnd('character', -(4-res[0].length))
rng.select()
}
}else{
rng.text = '\t' + str.replace(/\n/g,'\n\t')
}
return false
}
getテキストカーソル位置=function(elem){
// 左記URLのページを参照させていただきました。 http://d.hatena.ne.jp/nakazawaken1/20100125/p1
var rngSel=document.selection.createRange(), rngElem=document.body.createTextRange()
rngElem.moveToElementText(elem)
// rngSelより前の範囲を選択する(rngElemのEndをrngSelのStart位置に合わせる)
rngElem.setEndPoint('EndToStart', rngSel)
var f=function(rng){
var str=rng.text, str0=str
// 選択文字数が0でない間(前の範囲)
while(rng.compareEndPoints('StartToEnd', rng) != 0){
// 一文字ずつ範囲終了位置を前にずらしていく
rng.moveEnd('character', -1)
// ずらす前と後の文字列が異なる状態ならbreak
if(str0 != rng.text){break}
// ずらす前と後の値が同じ状態の間は改行を付け足していく。
str += '\r\n'
}
return str
}
var str前=f(rngElem), str選択=f(rngSel), start=str前.length, end=start + str選択.length
return {start:start, end:end}
}
inp行.onchange = inp列.onchange = function(){ setカーソル位置(ta, inp行.value, inp列.value) }
setカーソル位置=function(elem, 行,列){
var arr=ta.value.split('\r\n'), rng=document.body.createTextRange()
行 = isFinite(行) ? 行 : 1
列 = isFinite(列) ? 列 : 1
rng.moveToElementText(elem)
rng.moveStart('character', arr.slice(0,行).join('\r\n').length - (行-1))
rng.moveStart('character', 列 - arr[行-1].length - 1)
while(rng.compareEndPoints('StartToEnd', rng) != 0){ rng.moveEnd('character',-1) }
rng.select()
}
</script>
</html>


昨日の記事で「lineHeightが未定義でも取得する!」とか言ってましたが今回のサンプルにそれを使ってみたら微妙に値が小さくて改行するたびに水平線が徐々にカーソルの↑へ行ってしまったのでやむなくlineHeightを設定しました。


解像度が高くなくても液晶画面のバックライトが控えめだとかコントラストが低いとか目が疲れてるとか色々要因はありますが…。

0 件のコメント:

コメントを投稿