Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vue.jsで入力フォームにリアルタイムハイライト機能を自前実装した話

 Vue.jsで入力フォームにリアルタイムハイライト機能を自前実装した話

「UV Study : Vue.js LT会」の登壇資料。
https://uniquevision.connpass.com/event/326790/

coconala_engineer

August 22, 2024
Tweet

More Decks by coconala_engineer

Other Decks in Technology

Transcript

  1. Copyright coconala Inc. All Rights Reserved. • 社内では「よしみん」と呼ばれています • 会う度に髪色が違うと言われていました

    ◦ 最近は金髪に落ち着き気味 • 新卒では営業、総務を経て • 2019年エンジニアへキャリアチェンジ ◦ サーバーサイド、フロントエンド両方経験 • 2022年フロントエンドエンジニアとして中途入社 自己紹介 1 4 吉見 夢輝(Yumeka Yoshimi) 株式会社ココナラ プロダクト開発部 フロントエンド開発グループ 経歴
  2. Copyright coconala Inc. All Rights Reserved. やりたいこと 3 9 textareaにcssで装飾することはもちろんできます!

    が、入力した文字に対するハイライトのような装飾の仕方はできません。 [Q] cssで装飾するだけじゃない? <style lang="scss"> textarea { color: #fc6674; } </style> <style lang="scss"> textarea { background-color: #fc6674; } </style>
  3. Copyright coconala Inc. All Rights Reserved. やりたいこと 3 10 Vue.jsでtextareaにハイライトをつける機能のライブラリを調べると、下記のようなライブラリがヒットしま

    す。 ・vue-hilight-textarea:https://github.com/deerchao/vue-hilight-textarea 今回実装したいのは、 UIフレームワークを使用している 入力フォーム(既に textareaがラップされている)※ 1 を使用している箇所 UIフレームワークには既に textareaが含まれている為、textareaを含むライブラリ使用だと難しい [Q] ライブラリを使えば簡単に実装できるのでは? ※1を、さらにラップして使用できるようなハイライト機能コンポーネントを自前実装する
  4. Copyright coconala Inc. All Rights Reserved. 12 ハイライト要素 入力フォームコンポーネント (既にtextareaがラップされている)

    textareaで文字入力 文字の後ろにハイライトとなる色を装飾して配置 3 やりたいこと textareaと別にハイライト用の要素を用意
  5. Copyright coconala Inc. All Rights Reserved. 実装内容 4 14 1.ハイライトを適用したい文字を判定してハイライト処理するメソッドを用意する

    
 const replaceHighlightsText = (val: string) => { const escapeTextValue = val.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#039;') let newTextValue = escapeTextValue .replace(/ハイライト/g, '<mark>$&</mark>') if (!newTextValue.match(/<br>$/)) { newTextValue = `${newTextValue}<br>` } textValue.value = newTextValue.replace(/\r?\n/g, '<br>') } ハイライトしたい文字列を判定し、 markタグで囲う (今回は「ハイライト」の文字列) 入力された文字をエスケープ処理 &,<,>,",'の文字を文字コードに置き換える 改行コードを<br>タグに置き換える textareaタグでは改行コードで改行されるが、 今回見た目は        のdivタグとなる ハイライト要素 ※textValue.value ハイライト要素 のv-htmlに適用している文字列
  6. Copyright coconala Inc. All Rights Reserved. ${el}から${property}のstyleを 取得するメソッド 実装内容 4

    15 2.textareaの装飾をハイライト要素に適用するメソッドを用意する const synchronizeStyle = () => { if (!$textField.value) return Object.assign(backdropStyle, { width: getStyle($textField.value, 'width', false), height: getStyle($textField.value, 'height', false), letterSpacing: getStyle($textField.value, 'letterSpacing', false), font: getStyle($textField.value, 'font', false), borderWidth: getStyle($textField.value, 'borderWidth', false), borderStyle: getStyle($textField.value, 'borderStyle', false), padding: getStyle($textField.value, 'padding', false), caretColor: getStyle($textField.value, 'caretColor', false), backgroundColor: getStyle($textField.value, 'backgroundColor', false) }) } 入力フォームコンポーネン ト 内のtextarea ※$textField.value ※backdropStyle ハイライト要素 をラップしている styleオブジェクト ※getStyle(el, property, numericalize)
  7. Copyright coconala Inc. All Rights Reserved. 2で作成したメソッド textareaの装飾をハイライト要素に適用する 実装内容 4

    16 3.1,2で作成したメソッドを画面表示時に呼び出す 
 onMounted(() => { if (!textFieldHighlighter.value) return const el = textFieldHighlighter.value.querySelector('textarea') if (!el) return $textField.value = el replaceHighlightsText(el.value) synchronizeStyle() listenEvents() }) 1で作成したメソッド ハイライトを適用したい文字を判定して ハイライト処理する 入力フォームコンポーネント 内のtextareaを取得 ※4で説明 textareaのinputイベントへ ハイライト処理をイベントリスナー登録する
  8. Copyright coconala Inc. All Rights Reserved. 実装内容 4 17 4.textareaのinputイベントへハイライト処理をイベントリスナー登録する

    
 const onInput = e => { replaceHighlightsText(e.target.value) } const listenEvents = () => { if (!$textField.value) return $textField.value.addEventListener('input', onInput) } 1で作成したメソッド ハイライトを適用したい文字を判定して ハイライト処理する 入力フォームコンポーネント 内のtextarea ※$textField.value
  9. Copyright coconala Inc. All Rights Reserved. 実装内容 4 18 4.textareaのinputイベントへハイライト処理をイベントリスナー登録する

    
 const removeEvents = () => { if (!$textField.value) return $textField.value.removeEventListener('input', onInput) } onBeforeUnmount(() => { removeEvents() }) 入力フォームコンポーネント 内のtextarea ※$textField.value 登録したイベントリスナーは残り続けてしまうので、イベントリスナーの解除も行う
  10. Copyright coconala Inc. All Rights Reserved. 実装内容 4 19 リアルタイムハイライト機能のコンポーネントの

    template <template> <div ref="textFieldHighlighter" class="textFieldHighlighter" > <div class="textFieldHighlighter_backdrop" :style="backdropStyle" > <div v-html="textValue" class="textFieldHighlighter_highlights" :style="highlightsStyle" /> </div> <slot /> </div> </template> textareaを含んでいるコンポーネントを配置する ハイライト要素 をラップしている要素 ハイライト要素
  11. Copyright coconala Inc. All Rights Reserved. 実装内容 4 20 リアルタイムハイライト機能

    のコンポーネントの style <style lang="scss"> .textFieldHighlighter { position: relative; &_backdrop { position: absolute; z-index: 10; overflow: auto; pointer-events: none; border-color: transparent; } &_highlights { height: 100%; white-space: pre-wrap; word-wrap: break-word; color: transparent; position: relative; mark { color: transparent; background-color: #fc6674; // ハイライトの色指定 opacity: 0.2; } } textarea { display: block; z-index: 11 !important; margin: 0; background-color: transparent; overflow: auto; resize: none; } } </style> ハイライト要素 ハイライト要素 をラップしている要素 入力フォームコンポーネント 内のtextarea
  12. Copyright coconala Inc. All Rights Reserved. 実装内容 4 21 リアルタイムハイライト機能のコンポーネント使用箇所

    <script setup lang="ts"> import TextFieldHighlighter from '~/components/TextFieldHighlighter.vue' </script> <template> <TextFieldHighlighter> <OInput type="textarea" /> </TextFieldHighlighter> </template> 入力フォームコンポーネント
  13. Copyright coconala Inc. All Rights Reserved. 詰まりポイント 5 24 const

    onScroll = e => { if (!$textField.value) return Object.assign(highlightsStyle, { height: `${getStyle($textField.value, 'height') + e.target.scrollTop}px`, top: `-${e.target.scrollTop}px` }) } イベントリスナーの登録と解除を行う $textField.value.addEventListener('scroll', onScroll) $textField.value.removeEventListener('scroll', onScroll) 入力フォームコンポーネント 内のtextarea ※$textField.value onMounted時 onBeforeUnmount時 ※highlightsStyle ハイライト要素 のstyleオブジェクト
  14. Copyright coconala Inc. All Rights Reserved. 最後に 6 27 More

    Info ココナラ エンジニアのX(Twitter) X /Twitter(@coconala_eng) https://twitter.com/coconala_eng 採用ホームページ https://coconala.co.jp/recruit エンジニア採用ホームページ https://coconala.co.jp/recruit/engineer ココナラの人と組織を伝えるブログ ココナラLIVE https://blog.coconala.co.jp/m/m4e4abe8b17e5 人生の可能性を広げたユーザーストーリー わたしのスキル解放記 https://blog.coconala.co.jp/m/me8a586112ad2 ココナラに所属するエンジニアによるブログ テックブログ https://zenn.dev/coconala