Slide 1

Slide 1 text

Copyright coconala Inc. All Rights Reserved. Vue.jsで入力フォームに リアルタイムハイライト機能を自前実装した話 DATA : 2024.08.20 by Yumeka Yoshimi

Slide 2

Slide 2 text

Copyright coconala Inc. All Rights Reserved. Agenda 自己紹介 会社紹介 やりたいこと 実装内容 詰まりポイント 最後に 2 1 2 3 4 5 6

Slide 3

Slide 3 text

Copyright coconala Inc. All Rights Reserved. 自己紹介 Chapter 01 3

Slide 4

Slide 4 text

Copyright coconala Inc. All Rights Reserved. ● 社内では「よしみん」と呼ばれています ● 会う度に髪色が違うと言われていました ○ 最近は金髪に落ち着き気味 ● 新卒では営業、総務を経て ● 2019年エンジニアへキャリアチェンジ ○ サーバーサイド、フロントエンド両方経験 ● 2022年フロントエンドエンジニアとして中途入社 自己紹介 1 4 吉見 夢輝(Yumeka Yoshimi) 株式会社ココナラ プロダクト開発部 フロントエンド開発グループ 経歴

Slide 5

Slide 5 text

Copyright coconala Inc. All Rights Reserved. 会社紹介 Chapter 02 5

Slide 6

Slide 6 text

Copyright coconala Inc. All Rights Reserved. 会社紹介 2 6

Slide 7

Slide 7 text

Copyright coconala Inc. All Rights Reserved. やりたいこと Chapter 03 7

Slide 8

Slide 8 text

Copyright coconala Inc. All Rights Reserved. やりたいこと 3 入力した文字に連動して NGワードに ハイライトがつく textareaを実装したい 8

Slide 9

Slide 9 text

Copyright coconala Inc. All Rights Reserved. やりたいこと 3 9 textareaにcssで装飾することはもちろんできます! が、入力した文字に対するハイライトのような装飾の仕方はできません。 [Q] cssで装飾するだけじゃない? textarea { color: #fc6674; } textarea { background-color: #fc6674; }

Slide 10

Slide 10 text

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を、さらにラップして使用できるようなハイライト機能コンポーネントを自前実装する

Slide 11

Slide 11 text

Copyright coconala Inc. All Rights Reserved. 11 どう実装するのか? 3 やりたいこと textareaと別にハイライト用の要素を用意

Slide 12

Slide 12 text

Copyright coconala Inc. All Rights Reserved. 12 ハイライト要素 入力フォームコンポーネント (既にtextareaがラップされている) textareaで文字入力 文字の後ろにハイライトとなる色を装飾して配置 3 やりたいこと textareaと別にハイライト用の要素を用意

Slide 13

Slide 13 text

Copyright coconala Inc. All Rights Reserved. 実装内容 Chapter 04 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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イベントへ ハイライト処理をイベントリスナー登録する

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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 登録したイベントリスナーは残り続けてしまうので、イベントリスナーの解除も行う

Slide 19

Slide 19 text

Copyright coconala Inc. All Rights Reserved. 実装内容 4 19 リアルタイムハイライト機能のコンポーネントの template
textareaを含んでいるコンポーネントを配置する ハイライト要素 をラップしている要素 ハイライト要素

Slide 20

Slide 20 text

Copyright coconala Inc. All Rights Reserved. 実装内容 4 20 リアルタイムハイライト機能 のコンポーネントの style .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; } } ハイライト要素 ハイライト要素 をラップしている要素 入力フォームコンポーネント 内のtextarea

Slide 21

Slide 21 text

Copyright coconala Inc. All Rights Reserved. 実装内容 4 21 リアルタイムハイライト機能のコンポーネント使用箇所 import TextFieldHighlighter from '~/components/TextFieldHighlighter.vue' 入力フォームコンポーネント

Slide 22

Slide 22 text

Copyright coconala Inc. All Rights Reserved. 詰まりポイント Chapter 05 22

Slide 23

Slide 23 text

Copyright coconala Inc. All Rights Reserved. 詰まりポイント 5 23 テキストエリア内をスクロールすると ハイライトがずれてしまう

Slide 24

Slide 24 text

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オブジェクト

Slide 25

Slide 25 text

Copyright coconala Inc. All Rights Reserved. 最後に Chapter 06 25

Slide 26

Slide 26 text

Copyright coconala Inc. All Rights Reserved. 最後に 6 ココナラでは エンジニアを募集しております 26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Copyright coconala Inc. All Rights Reserved. 最後に 6 28 今回の実装内容は テックブログでも公開しています

Slide 29

Slide 29 text

一人ひとりが 「自分のストーリー」を 生きていく世の中をつくる ユーザーにとって見えていなかった無数の可能性の扉を開く、 そこにエンジニアとして一緒に貢献しませんか。