AI事業本部 オンライン接客事業部の田中です。
オンライン接客システム「リモてなし」の機能の一部である資料共有について、どのように実現したかご紹介したいと思います。
資料共有とはどんな機能?
資料共有とはオンライン上で接客スタッフが説明資料(画像や動画)をお客様と一緒に見ながら接客できるようにするための機能です。
手書きや図形の書きこみ、ユーザー相互のカーソル位置の表示などができます。
なぜ資料共有が必要なの?
この機能の開発時には大きく分けて3つの解決したい課題がありました
- 対面で接客するように、画像や動画をお客様と一緒に見たい
- 紙の資料に手書きでマーキングしたり指さしするように説明箇所がわかるようにしたい
- オンラインだとお客様が資料のどこを見ているかわからない
どのように解決するの?
一つ目の課題は画像や動画を接客スタッフと顧客の間で共有して同期できるようにします。Canvasを使って動的に画像を表示させます。また、画像をより共有しやすくするために画像の拡大縮小やドラッグで移動もできるようにします。
二つ目の課題を解決するには、接客スタッフが共有した画像や動画上に図形を描画してそれを共有できるようにする必要があります。また、指さしのように説明箇所を伝えるために、画像上の接客スタッフのカーソル位置を顧客の画像上に表示するようにします。リモてなしではこれをライブカーソル機能と呼んでいます。
三つ目を解決するには、単純に同じ画像を共有して表示するだけでなく、共有した画像のズーム比率や縦横比についても接客スタッフと顧客間で同期させる必要があります。ユースケース上、Canvasの枠内に説明資料を大きく表示させたい関係で、資料を表示する領域の縦横比が動的に変わるようになっています。
技術要素と利用したライブラリ
技術要素としては以下の内容がありました
- 解決したい課題が複雑かつコア機能の一つなので自由度が高いライブラリを使いたい
- 図形描画のしやすさ
- 描画時のパフォーマンス
この記事では選定の詳細については割愛しますが、上記のような理由からKonvaを採用しました。KonvaとはHTML Canvas上に2Dグラフィックを表示したり、ユーザーがグラフィックを操作するためのUIを提供するライブラリです。公式ページのデモを見ていただくと、どのようなことができるかわかりやすいです。
技術要素の詳細
以下ではKonvaを使ってどのように課題を解決したかをいくつか紹介したいと思います。
資料表示のためのレイヤー構造
まずは、資料と描画図形をどのように表示しているか説明します。資料画像や図形を表示するため4つのレイヤーを用意しています。このようにレイヤーを分けることで各レイヤーの拡大縮小や位置の調整などが個別に管理しやすくなります。一番下のレイヤーは資料の画像を表示しています。2番目は画像操作のためのスクロールバーを表示しています。3番目は図形表示のためのレイヤー、4番目はライブカーソルのレイヤーになっています。
以下のサンプルのようにStageに対して各レイヤーを追加していきます。後に追加するレイヤーが上に表示されます。また、動的にレイヤーの上下は変更することができます。
const stage = new Konva.Stage({
container: 'konva'
})
const imageLayer = new Konva.Layer({
id: 'image-layer',
})
const scrollbarLayer = new Konva.Layer({
id: 'scrollbar-layer',
})
const drawLayer = new Konva.Layer({
id: 'draw-layer',
})
const cursorLayer = new Konva.Layer({
id: 'corsor-layer',
})
stage.add(imageLayer)
stage.add(scrollbarLayer)
stage.add(drawLayer)
stage.add(cursorLayer)
接客スタッフと顧客間の同期
同期については資料画像、手書きや図形の描画オブジェクトとカーソル位置があり、課題1と3を解決するために必要になります。
接客スタッフと顧客の資料画像を同期させる
接客スタッフと顧客間で同期しなければいけない要素として以下があります。
- 接客スタッフの画像の縦横比
- 画像の拡大縮小率
- 画像の位置
- スクロールバーの位置
各要素が変化するタイミング(イベント)は複数あるためそれらに対応しなければいけません。例えば、画像の縦横比についてはブラウザ自体のサイズを変えたときや画像の外側のDOMサイズが変わった場合、画像の拡大縮小率を変更したタイミングで同期を取る必要があります。
また、これらの同期のためのイベントはWebSocketを使用して接客スタッフから顧客に対して同期メッセージを送っていますが、顧客が途中から参加する場合もあります。この場合顧客は接客に参加前の同期メッセージは受け取れないので、何らかの方法で受け取れるようにする必要があります。解決方法として今回は接客スタッフが定期的に同期メッセージ送る仕組みにしました。定期的なメッセージ送信のために requestAnimationFrame を使ったループを実装しています。これを使っている理由はメッセージ送信の間隔を動的に調整することができるためです。 例えば、接客スタッフのWebブラウザがバックグランドのタブに隠れた場合は、処理の間隔を長くしてパフォーマンスを向上させることができます。
描画の同期
接客スタッフが描いた手書きや図形を顧客の資料に表示するために必要になります。手書きに関してはパフォーマンス向上のため座標を逐次同期メッセージとして送らずに、マウスダウンからマウスアップまでの全ての座標や描画に必要な色や太さなどの情報をまとめて一つの同期メッセージとして顧客に送信しています。
ライブカーソルを表示のためのカーソル座標同期
課題2の指さしを実現するために、ライブカーソル利用時は接客スタッフのカーソル位置を定期的に取得しています。ここでも requestAnimationFrame を使用しています。また移動した接客スタッフのカーソルの座標を間引いて同期メーセージとして顧客に送信しています。顧客側で受け取った座標情報をそのままカーソルオブジェクトとして表示すると滑らかなカーソル移動を再現できません。そのため、同期メッセージから取得したカーソルの座標情報を一度キューイングした後、キューから座標を取り出して Animation を使って間引いたカーソル座標を補完しながら接客スタッフのカーソル移動を再現させています。このようにすることで滑らかなカーソル移動が実現できます。
ちなみに、Konva canvas上の図形(node)を削除して再生成を繰り返す方法もありますが、移動中のオブジェクトのチラつきやガクつきがありパフォーマンスも良くないのでお勧めしません。
cursors[userId].animation = new Konva.Animation((_) => {
// ユーザー毎にカーソルオブジェクトを移動させる
}, liveCursorLayer)
Konva canvasをレスポンシブにする
Konva canvasのサイズや資料画像、描画した図形のサイズはWebブラウザのサイズを変更してもデフォルトでは変わってくれません。
Konva canvasをレスポンシブにするための方法として2つあります。
今回は2番目の動的に変更する方法を採用しました。
イメージとしては以下のように各レイヤーの各ノードをアップデートしていきます。
layer.children.forEach((node) => {
// 各node(図形など)の位置や大きさを再設定する
node.setAttrs({
x: newPosX,
y: newPosY,
width: newWidth,
height: newHeight,
})
同期を保存する
接客スタッフと顧客間で資料画像の同期は取れますが、ユーザーがリロードしてしまうとそれらの情報は初期化されてしまいます。それを解決するために別記事で紹介されているYjsを使って、Webブラウザをリロードしてしまっても途中から再開できる仕組みづくりも進めています。
まとめ
ここに紹介していない仕組みなどまだまだありますが、上記で説明した様々な工夫を凝らして接客スタッフと顧客の資料共有体験を向上させることができました。これにより、企業様からはとても高い評価を得ることができました。
リモてなしチームでは、接客の新時代を一緒に築き上げるメンバーを絶賛募集中です!
ちょっとでも気になった方は、カジュアルにお話するところから始めましょう!
こちらのリンクから気軽にご連絡ください!