<!DOCTYPE html>
<html lang="ja" >
<head>
<meta charset="UTF-8" >
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" >
<title>ツーサム入力 v3 (変換対応)</title>
<style>
:root {
--bg-color: #222;
--text-color: #fff;
--accent-color: #00b4d8; /* 選択中の色 */
--confirm-color: #ff006e; /* 決定ボタン */
--panel-bg: #333;
}
body {
margin: 0 ; padding: 0 ;
background-color: var (--bg-color);
color: var (--text-color);
font-family: sans-serif;
height: 100 vh;
width: 100 vw;
display: flex;
flex-direction: column;
overflow: hidden;
user-select : none;
touch-action: manipulation;
}
#display-area {
height: 15 vh;
background: #fff;
color: #000;
font-size: 24 px;
padding: 10 px;
display: flex;
flex-direction: column;
justify-content: center;
border-bottom: 2 px solid #555;
}
#input-line {
white-space: nowrap;
overflow-x: auto;
}
.composing {
border-bottom: 2 px solid blue;
background-color: #e0f7fa;
}
#candidate-view {
font-size: 14 px;
color: #555;
height: 20 px;
margin-top: 5 px;
}
#keyboard {
flex: 1 ;
display: grid;
grid-template-columns: 1.5f r 1f r;
gap: 2 px;
padding: 2 px;
}
#left-panel {
display: grid;
grid-template-columns: 1f r 1f r;
grid-template-rows: repeat(6 , 1f r);
gap: 2 px;
}
#right-panel {
display: grid;
grid-template-columns: 1f r;
grid-template-rows: repeat(5 , 1f r);
gap: 2 px;
}
.key {
display: flex;
align-items: center;
justify-content: center;
font-size: 18 px;
font-weight: bold;
background-color: var (--panel-bg);
border-radius: 4 px;
cursor: pointer;
position: relative;
}
.key-left.active {
background-color: var (--accent-color);
color: #000;
border: 2 px solid #fff;
}
.key-right {
font-size: 26 px;
background-color: #444;
}
.func-key { background-color: #555; font-size: 16px; }
.convert-key { background-color: #2a9d8f; color: #fff; }
.nav-btn { background-color: #4a4e69; color: #fff; font-size: 20px; }
.confirm-btn { background-color: var (--confirm-color); font-weight: 900 ; font-size: 24 px; }
.key:active { opacity: 0.7 ; transform: scale(0.98 ); }
</style>
</head>
<body>
<div id="display-area" >
<div id="input-line" >
<span id="committed-text" ></span><span id="composing-text" class ="composing" ></span>|
</div>
<div id="candidate-view" >未入力</div>
</div>
<div id="keyboard" >
<div id="left-panel" >
<div class ="key key-left" onclick="setRow('a', this)" >あ行</div>
<div class ="key key-left" onclick="setRow('k', this)" >か行</div>
<div class ="key key-left" onclick="setRow('s', this)" >さ行</div>
<div class ="key key-left" onclick="setRow('t', this)" >た行</div>
<div class ="key key-left" onclick="setRow('n', this)" >な行</div>
<div class ="key key-left" onclick="setRow('h', this)" >は行</div>
<div class ="key key-left" onclick="setRow('m', this)" >ま行</div>
<div class ="key key-left" onclick="setRow('y', this)" >や行</div>
<div class ="key key-left" onclick="setRow('r', this)" >ら行</div>
<div class ="key key-left" onclick="setRow('w', this)" >わ行</div>
<div class ="key func-key" onclick="deleteChar()" >削除</div>
<div class ="key func-key convert-key" onclick="startConversion()" >変換</div>
</div>
<div id="right-panel" >
<div class ="key key-right" id="r0" onclick="handleRight(0)" >あ</div>
<div class ="key key-right" id="r1" onclick="handleRight(1)" >い</div>
<div class ="key key-right" id="r2" onclick="handleRight(2)" >う</div>
<div class ="key key-right" id="r3" onclick="handleRight(3)" >え</div>
<div class ="key key-right" id="r4" onclick="handleRight(4)" >お</div>
</div>
</div>
<script>
const charMap = {
'a' : ['あ' ,'い' ,'う' ,'え' ,'お' ],
'k' : ['か' ,'き' ,'く' ,'け' ,'こ' ],
's' : ['さ' ,'し' ,'す' ,'せ' ,'そ' ],
't' : ['た' ,'ち' ,'つ' ,'て' ,'と' ],
'n' : ['な' ,'に' ,'ぬ' ,'ね' ,'の' ],
'h' : ['は' ,'ひ' ,'ふ' ,'へ' ,'ほ' ],
'm' : ['ま' ,'み' ,'む' ,'め' ,'も' ],
'y' : ['や' ,'(' ,'ゆ' ,')' ,'よ' ],
'r' : ['ら' ,'り' ,'る' ,'れ' ,'ろ' ],
'w' : ['わ' ,'を' ,'ん' ,'ー' ,'、' ]
};
const dictionary = {
'さ' : ['差' , '佐' , '左' , 'さ' ],
'し' : ['氏' , '市' , '四' , 'し' ],
'あい' : ['愛' , '藍' , '相' , 'あい' ],
'きかい' : ['機械' , '機会' , '奇怪' , 'きかい' ],
'こうせい' : ['構成' , '校正' , '公正' , '後世' , 'こうせい' ],
'わたし' : ['私' , '渡し' , 'わたし' ],
'にほん' : ['日本' , '二本' , 'にほん' ]
};
let currentRow = 'a' ;
let committedStr = "" ;
let composingStr = "" ;
let isConverting = false ;
let candidates = [];
let candidateIndex = 0 ;
const elCommitted = document.getElementById('committed-text' );
const elComposing = document.getElementById('composing-text' );
const elCandView = document.getElementById('candidate-view' );
const rightKeys = [
document.getElementById('r0' ),
document.getElementById('r1' ),
document.getElementById('r2' ),
document.getElementById('r3' ),
document.getElementById('r4' )
];
highlightLeftRow();
function setRow (rowKey, element ) {
if (isConverting) {
cancelConversion();
}
currentRow = rowKey;
document.querySelectorAll('.key-left' ).forEach(k => k.classList.remove ('active' ));
if (element) element.classList.add ('active' );
updateRightLabels();
}
function highlightLeftRow ( ) {
document.querySelectorAll('.key-left' ).forEach(k => k.classList.remove ('active' ));
}
function updateRightLabels ( ) {
const chars = charMap[currentRow];
rightKeys.forEach((key, i) => {
key.innerText = chars[i];
key.className = "key key-right" ;
key.style.backgroundColor = "" ;
});
}
function handleRight (index ) {
if (isConverting) {
if (index === 0 ) {
moveCandidate(-1 );
} else if (index === 1 ) {
moveCandidate(1 );
} else if (index === 4 ) {
confirmConversion();
} else if (index === 3 ) {
cancelConversion();
}
} else {
const char = charMap[currentRow][index];
composingStr += char ;
updateDisplay();
}
}
function startConversion ( ) {
if (composingStr === "" ) return ;
isConverting = true ;
if (dictionary[composingStr]) {
candidates = dictionary[composingStr];
} else {
candidates = [composingStr];
}
candidateIndex = 0 ;
changeRightPanelToControl();
updateDisplay();
}
function changeRightPanelToControl ( ) {
rightKeys[0 ].innerText = "▲ 前へ" ;
rightKeys[0 ].className = "key nav-btn" ;
rightKeys[1 ].innerText = "▼ 次へ" ;
rightKeys[1 ].className = "key nav-btn" ;
rightKeys[2 ].innerText = "" ;
rightKeys[2 ].className = "key" ;
rightKeys[2 ].style.backgroundColor = "#222" ;
rightKeys[3 ].innerText = "戻る" ;
rightKeys[3 ].className = "key nav-btn" ;
rightKeys[4 ].innerText = "確定" ;
rightKeys[4 ].className = "key confirm-btn" ;
}
function moveCandidate (dir ) {
candidateIndex += dir;
if (candidateIndex < 0 ) candidateIndex = candidates.length - 1 ;
if (candidateIndex >= candidates.length) candidateIndex = 0 ;
updateDisplay();
}
function confirmConversion ( ) {
committedStr += candidates[candidateIndex];
composingStr = "" ;
isConverting = false ;
setRow(currentRow, document.querySelector(`.key-left[onclick*="'${currentRow}'" ]`));
updateDisplay();
}
function cancelConversion ( ) {
isConverting = false ;
setRow(currentRow, null );
updateDisplay();
}
function deleteChar ( ) {
if (isConverting) {
cancelConversion();
return ;
}
if (composingStr.length > 0 ) {
composingStr = composingStr.slice(0 , -1 );
} else if (committedStr.length > 0 ) {
committedStr = committedStr.slice(0 , -1 );
}
updateDisplay();
}
function updateDisplay ( ) {
elCommitted.innerText = committedStr;
if (isConverting) {
elComposing.innerText = candidates[candidateIndex];
elComposing.style.borderBottom = "2px solid #ff006e" ;
elCandView.innerText = `候補 [${candidateIndex+1 }/${candidates.length}]: ${candidates.join (" " )}`;
} else {
elComposing.innerText = composingStr;
elComposing.style.borderBottom = composingStr ? "2px solid blue" : "none" ;
elCandView.innerText = "文字を入力してください..." ;
}
}
</script>
</body>
</html>
copy
コメント