インプレッション数を数える機能を作りたい!
Scrollイベント、ポーリングを使いこなしたい!
こういった方に参考になる記事です!
この記事を書いている私(@Shoot58153748)は、
2020年2月現在メガベンチャーの社内スタートアップの部署でエンジニア(1年目)をしており、
プログラミング未経験からメガベンチャーへの転職を成功させた経験・ノウハウ
Webエンジニアになってから学んだこと
をブログにまとめています。
前回、前々回は通報機能(画面、API)を実装しましたが、
今回から2記事(フロント側実装、API実装)にわたって、インプレッション数計測機能の実装をしていきます!
フロント編では、
「ポーリング」
という技術についてピックアップします。
他にもVueでのスクロールイベント実装や
画面の位置情報取得など、
ぜひ参考にしてください!
今回のポイント
- ポーリング
- スクロールイベント
- 画面位置情報取得
今までのLaravelでのWebアプリ開発記事もまとめていますので、
こちらも参考にしてください!
また、ブログでは最低限必要な(更新した)部分のみ掲載するので、
全体のソースコードをみたい場合は以下にあげてあります。
Github: https://github.com/Shuto-san/laravel-vue-docker
インプレッション数とは?
インプレッション数
とは、広告が表示された回数のこと。
Web広告業界では、自社の広告が見られた(表示された)数は、とても重要な指標の1つです。
ツイッターでもインプレッション数を見ることができますよね?
広告じゃなくても、対象のものが何回見られたというデータを管理することは、
ビジネス上とても役に立ちそうです。
そこで今回は、各ツイートが見られた回数(インプレッション数)を数える機能のフロント側を作ります。(次回はサーバー側作ります)
特に押さえてほしいポイントとして、ポーリング
という技術を用いてサーバー側と通信していきます。
ポーリングとは
ポーリング(polling)
とは、ざっくり言うと
「WebブラウザとWebサーバが定期的に通信するための技術」
です。
通信の競合を避けたり、更新データにリアルタイム(風)にアクセスしたい場合使用されます。
実は、WebブラウザとWebサーバが定期的に通信するための技術は他にもあります。
通常WebサーバとWebブラウザは、HTTPプロトコルで通信し、
クライアント(ブラウザ)からリクエストしてサーバからレスポンスが返りますよね?
そのため、基本的にはサーバ側からブラウザに新着情報をリアルタイムで通知(プッシュ)できるようにはできていません。
しかしそれでも通知(プッシュ)したい場合、いくつか方法があります。ポーリング
、Websocket
、COMET
、SSE
…etcポーリング
は、クライアントからサーバに定期的に新着を問い合わせるようにします。
ポーリング間隔の分だけ通知が遅延してしまいますが、最も原始的かつ確実なやり方です。
今回、JavaScriptで実装していきます!
画面イメージ
インプレッション計測機能実装例(フロント側)
今回の制作物(実装内容)は以下になります。
- 画面
- Js
画面
ファイル名:resources/views/index.blade.php
<div class="tweet-timeline">
//////////////////////////修正部分//////////////////////////////
<div class="tweet-card" v-for="tweet in tweets" :key="tweet.id" :id="tweet.id" :class="{unviewed: !tweet.is_viewed}" v-cloak>
//////////////////////////////////////////////////////////////
<div class="tweet-contents">
<div class="tweet-contents-tweet">
<div>@{{ tweet.tweet }}</div>
</div>
<div class="tweet-contents-footer">
<i v-if="tweet.is_liked" class="fas fa-heart" @click="pushLike(tweet)"></i>
<i v-else class="far fa-heart" @click="pushLike(tweet)"></i>
<i v-if="tweet.is_reported" class="fas fa-flag" @click="openModal(tweet)"></i>
<i v-else class="far fa-flag" @click="openModal(tweet)"></i>
</div>
</div>
<section v-if="tweet.isDisplayed" class="modal-area">
<div class="modal-background"></div>
<div class="modal-wrapper">
<div class="modal-contents">
<h1 v-if="tweet.is_reported">通報を取り消しますか?</h1>
<h1 v-else>通報しますか?</h1>
<p>※意味不明なツイート、不快なツイート、誹謗中傷を含むツイートは通報できます</p>
<button v-if="tweet.is_reported" class="btn" @click="pushReport(tweet)">通報取消</button>
<button v-else class="btn" @click="pushReport(tweet)">通報する</button>
</div>
<div class="modal-close" @click="closeModal(tweet)">
×
</div>
</div>
</section>
</div>
</div>
<infinite-loading @infinite="fetchTweets"></infinite-loading>
UI自体は全く変わりませんが、tweet-card
のdiv
タグにクラスのバインディングを追加しました。:class="{unviewed: !tweet.is_viewed}"
サーバーからこのツイートがすでに表示されたかどうかの情報を受け取り、
まだ表示されていないツイートに関しては、unviewed
というクラスを追加
JSでunviewed
がついたツイートのみリクエスト内容に含めるという処理を書きます。
JS
ファイル名:resources/js/tweet.js
new Vue({
el: '#tweet',
data: {
...
userAction: {
like: {
list: [],
debouncedList: []
},
report: {
},
+ impression: {
+ tweetIdList: [], // インプレッション計測されたツイートのIDリスト
+ postedTweetIdList: [], // ポストされたツイートのIDリスト
+ updateTweetImpressionCountTimer: null // ポーリング対象の関数を格納
+ }
},
...
},
+ created: function() {
// ポーリングの実装部分。3秒に一回サーバーにリクエストを送る関数を実行する。(実際にリクエストを送らない場合もある)
+ this.updateTweetImpressionCountTimer = setInterval(this.pushImpressionCount, 3000)
// スクロールイベントを定義。スクロールされたらインプレッション数を計測する
+ window.addEventListener('scroll', this.countImpression);
+ },
methods: {
// インプレッション数を計測する処理部分
+ countImpression() {
+ let windowTop = pageYOffset;
+ let windowHeight = window.outerHeight;
+ let impressionTweetList = document.querySelectorAll(".unviewed");
+ for (let i = 0; i < impressionTweetList.length ; i++) {
+ let impressionTweetId = impressionTweetList[i].id;
+ let elemPosition = impressionTweetList[i].offsetTop + impressionTweetList[i].offsetHeight *3/4;
+ if (!(this.userAction.impression.tweetIdList.indexOf(impressionTweetId) >= 0)){
+ if (elemPosition > windowTop && elemPosition < windowTop + windowHeight) {
+ this.userAction.impression.tweetIdList.push(impressionTweetId);
+ }
+ }
+ }
+ },
// インプレッション数が計測されたツイートIDのリストを作成し、サーバー側に通知する
+ pushImpressionCount() {
+ let postTweetIdList = this.userAction.impression.tweetIdList.filter(id =>
+ !this.userAction.impression.postedTweetIdList.includes(id)
+ );
+ postTweetIdList.forEach(value => {
+ this.userAction.impression.postedTweetIdList.push(value);
+ });
+ if (postTweetIdList.length !== 0) {
+ this.postImpressionCount(postTweetIdList);
+ }
+ },
// サーバー側にポストする関数
+ postImpressionCount(tweetIdList) {
+ console.log('impression count request!');
+ console.log(JSON.stringify(tweetIdList));
+ axios.post(window.location.origin + `/tweet/impression`, {
+ impressionTweetIdList: JSON.stringify(tweetIdList)
+ })
+ .then(response => {
+ })
+ .catch(error => {
+ });
+ },
},
...
});
+
の行が今回追記した部分です。
今回の肝は、created
で定義する「スクロールイベント」と「setInterval」
実はこのsetInterval
がポーリングを実現する実装です。3000
ms毎にpushImpression
という関数を実行します。
変数は以下の通りtweetIdList
:インプレッション計測されたツイートのIDリストpostedTweetIdList
:ポストされたツイートのIDリストupdateTweetImpressionCountTimer
:ポーリング対象の関数を格納unviewed
というクラスを持つタグのみ(ツイートカード)をquerySelectorAll
で取得し、
JSで画面や要素の位置情報をします。
画面トップと画面下部の位置情報と、
各ツイートカードの要素の一番上と要素高さを取得
各ツイートカードの要素が画面トップと画面下部に収まっている場合、
(正確に言うと、要素の3/4が表示されていたら)tweetIdList
にツイートIDを追加していきます。
メソッドは以下の通りcountImpression
:画面がスクロールされたら実行される。画面に表示されたツイートを配列に追加する(画面に収まっているツイートのIDリストを作る)。pushImpression
:3秒に1回実行される関数。画面に表示されたツイートIDをサーバーに送る。すでにサーバー側に送られたツイートIDリスト(postedTweetIdList
)に含まれるツイートIDは除く。postImpression
:サーバーに通報情報をポストするnpm run dev
でJSをビルド
まとめ:ポーリングはJsのsetIntervalで
以上、インプレッション機能のフロント側の実装でした!
今回のポイント
- ポーリング
- スクロールイベント
- 画面位置情報取得
Webの開発現場では「インプレッション」「ポーリング」という単語が飛び交うことがあります。
開発者の会話についていけるかは、
その技術を知っているかどうかなので
これを機にポーリングについてぜひ学んでいきましょう。
画面の位置情報を取れる、ということも知っているだけで
実装の幅が広がりますよね!
次回はインプレッション機能のAPIを実装します!
それではまた!
コメント