textarea 内で、入力とともに強調表示を行うカスタム要素(HIGH-LIGHT)を作成しました。まだ、日本語入力やスマホへの対応が不十分なので用途を限定した使い方しかできません。あらゆるケースに対応するにはまだまだ時間がかかりそうです。
/* * Highlight keywords. * * s: input string * k: array of keywords * c: class name for css * */ var highlighting = function(s, k, c) { k.sort(function(a, b) { if (a < b) return 1; if (a > b) return -1; return 0; }).forEach(function(x) { s = s.replaceAll(x, `\x02${x}\x03`); }); s = s.replace(/\x02/g, `<span style="${c}">`); s = s.replace(/\x03/g, `</span>`); return s; }; jQuery(function($) { var k = [ '趙高', '王莽', '周伊', '禄山', '将門', '純友', '義親', '信頼', '清盛' ]; var d1 = document.querySelector('high-light#D1'); d1.highlight = function(html) { return highlighting(html, k, 'background-color:yellow;color:red'); }; var d2 = document.querySelector('high-light#D2'); d2.highlight = function(html) { const s = this.un_esc_html(html); const s1 = s.match(/^\s*/)[0]; const s2 = s.match(/\s*$/)[0]; const pre = new JSParser().parse(s); return s1 + $(pre).html() + s2; }; $('form').on('submit', function() { $('input[name=T1]').val(d1.value); $('input[name=T2]').val(d2.value); }); });
/* * class HighLight * * Date: 2024.3.29 * * Copyright (c) 2022, 2024 AOK (aokura.com) * */ class HighLight extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ delegatesFocus:true, mode:'open' }); shadowRoot.innerHTML = `<style> :host { box-sizing:border-box; display:block; font-size:16px; padding:0; position:relative; } textarea { background-color:transparent; border:none; border-radius:3px; box-sizing:border-box; caret-color:black; color:transparent; font:inherit; line-height:1.2; margin:0; outline:2px solid #ccc; overflow:auto; padding:4px; position:absolute; resize:none; word-break:break-word; } textarea:focus { outline-color:blue; } div { scrollbar-color:transparent transparent; } </style> <textarea spellcheck="false" part="textarea"></textarea> <div></div>`; this._textarea = shadowRoot.querySelector('textarea'); this._div = shadowRoot.querySelector('div'); } get value() { return this._textarea.value; } set value(v) { this.textContent = v; this._textarea.value = v; this._input(); } esc_html(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } un_esc_html(html) { var div = document.createElement('div'); div.innerHTML = html; return div.textContent; } _get_margin() { var p = window.getComputedStyle(this); var t = window.getComputedStyle(this._textarea); var r = [0, 0]; r[0] += parseInt(p.getPropertyValue('border-left-width')); r[1] += parseInt(p.getPropertyValue('border-top-width')); r[0] += parseInt(p.getPropertyValue('border-right-width')); r[1] += parseInt(p.getPropertyValue('border-bottom-width')); r[0] += parseInt(p.getPropertyValue('padding-left')); r[1] += parseInt(p.getPropertyValue('padding-top')); r[0] += parseInt(p.getPropertyValue('padding-right')); r[1] += parseInt(p.getPropertyValue('padding-bottom')); r[0] += parseInt(t.getPropertyValue('margin-left')); r[1] += parseInt(t.getPropertyValue('margin-top')); r[0] += parseInt(t.getPropertyValue('margin-right')); r[1] += parseInt(t.getPropertyValue('margin-bottom')); return r; } _outer_resize() { var r = this._get_margin(); var w = this.getBoundingClientRect().width; var h = this.getBoundingClientRect().height; if (w == 0) { w = this._textarea.getBoundingClientRect().width; this.style.setProperty('width', `${w+r[0]}px`); } else { this._textarea.style.setProperty('width', `${w-r[0]}px`); this._div.style.setProperty('width', `${w-r[0]}px`); } if (h == 0) { h = this._textarea.getBoundingClientRect().height; this.style.setProperty('height', `${h+r[1]}px`); } else { this._textarea.style.setProperty('height', `${h-r[1]}px`); this._div.style.setProperty('height', `${h-r[1]}px`); } } _inner_resize() { var r = this._get_margin(); var w = this._textarea.getBoundingClientRect().width; var h = this._textarea.getBoundingClientRect().height; var t = window.getComputedStyle(this._textarea); var v = t.getPropertyValue('resize'); if (v == 'horizontal' || v == 'both') { this.style.setProperty('width', `${w+r[0]}px`); this._div.style.setProperty('width', `${w}px`); } if (v == 'vertical' || v == 'both' || this.getAttribute('expand') == 'true') { this.style.setProperty('height', `${h+r[1]}px`); this._div.style.setProperty('height', `${h}px`); } } _expand_height() { if (this.getAttribute('expand') == 'true') { var style = window.getComputedStyle(this._div); this._textarea.style.setProperty('overflow-y', 'hidden'); this._div.style.setProperty('height', 'auto'); if (this._div.innerHTML == '') { this._div.innerHTML = ' '; } var h = style.getPropertyValue('height'); if (this._pre_height != h) { this._textarea.style.setProperty('height', h); this._pre_height = h; } this._div.style.setProperty('height', h); } } _input() { var s = this.esc_html(this._textarea.value); if (/\n$/.test(s)) s += ' '; if (this.highlight) { this._div.innerHTML = this.highlight(s); } else { this._div.innerHTML = s; } this._expand_height(); } _copy_css() { var t = window.getComputedStyle(this._textarea); const css_names = [ 'border', 'box-sizing', 'font', 'letter-spacing', 'line-height', 'margin', 'overflow', 'padding', 'text-decoration', 'white-space', 'word-break', 'word-spacing','vertical-align' ]; css_names.forEach(function(s) { this.style.setProperty(s, t.getPropertyValue(s)); }, this._div); } connectedCallback() { var a = this; document.addEventListener('DOMContentLoaded', function() { a._copy_css(); a._outer_resize(); a._textarea.value = a.textContent; a._input(); }); window.addEventListener('resize', function() { a._outer_resize(); a._expand_height(); }); a._textarea.addEventListener('scroll', function() { a._div.scrollTop = a._textarea.scrollTop; a._div.scrollLeft = a._textarea.scrollLeft; }); a._textarea.addEventListener('input', function() { a._input(); }); const observer = new MutationObserver(function() { a._copy_css(); a._inner_resize(); }); observer.observe(this._textarea, { attriblutes: true, attributeFilter: ['style'] }); a._textarea.addEventListener('compositionstart', function() { a._textarea.style.setProperty('color', 'inherit'); a._div.style.setProperty('visibility', 'hidden'); }); a._textarea.addEventListener('compositionend', function() { a._textarea.style.setProperty('color', 'transparent'); a._div.style.setProperty('visibility', 'visible'); }); } } customElements.define('high-light', HighLight);