20
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ユーザーさん。もうこのDOMエレメント既読済みですか?(angularで)

Last updated at Posted at 2015-05-10

#ユーザーさん。もうこのDOMエレメント既読済みですか?(Angularで)
メッセージアプリをangularjsを利用して作っているのですが、ユーザーがメッセージを既読したかどうかって、どうやって読み取ればいいのか探していて以下の結論に至りました。

サンプルはこちら↓↓↓
https://jsfiddle.net/h54sn6o7/1/
※AngularJS 1.2.1

##やりたいこと
スクロールしたときに、DOMエレメントがWindow内に入ったどうかを検出して、既読済みのメッセージを{read: true}にしていく。

##コード
###html

<div ng-app="myApp">
  <div ng-controller="MainCtrl as main">
    <ul ng-repeat="message in main.messages">
      <li msg content=message ng-class="{message: message.read }">{{message.text}}</li>
    </ul>
  </div>
</div>

###css

.message{
    color: red;
}

###js

"use strict";

angular.module("myApp", []);

angular.module("myApp").directive("msg", ["$window", function($window){
    return {
        restrict: "A",
        scope: {
            content: "="
        },
        link: function(scope, element){
            angular.element($window).bind('scroll', function () {
                var elementY = element[0].offsetTop;
                var pageTop = this.pageYOffset;
                var pageBottom = this.pageYOffset + this.innerHeight - 50;
                if(elementY > pageTop && elementY < pageBottom){
                    scope.content.read = true;
                    scope.$apply();
                }
            });
        }
    }
}]);

angular.module("myApp").controller("MainCtrl", function(){
    this.messages = [];
    for(var i = 0; i < 100; i++){
        this.messages[i] = {
            text: "message" + i,
            read: false
        }
    }
});

##考察
そんなに難しいことではないので、どっかに同じこと書いてる人いそう。
記法でまずいところがあったら教えてください。

##Angularではパフォーマンスを意識しなければいけない。
Angularって、すんなりやりたいことを書けてしまう反面、裏で何が行われているかしっかり理解しとかないとパフォーマンスが落ちてきますよね。
私自身、パフォーマンスまで意識が回らず、まずい書き方をしてしまう。
今回、@armorik83パイセンから助言をいただきました。

scrollやresizeといったウインドウイベント系はlodash.throttle()辺りでイベント発生を間引くのがベターで、そのままでは処理負荷がけっこう大きいです。
今回の例ではマウスホイールを少し転がすだけで数百イベントが発行され、ng-class="{message: message.read}"の時点で数百のDOM操作が発生しているので、数千msオーダーでパフォーマンスが悪化します。

なるほど。実際に実装する時は$timeoutとかで間引いいていたのですが、lodashを使う方法もあると。こっちの方がコード量が減りますね。
https://lodash.com/docs#throttle
https://jsfiddle.net/h54sn6o7/3/
こんな感じでしょうか。
パイセン。いつもお世話になります。

##と思いきや
このやり方は少し違っているようで

ウインドウイベントをlodash.throttle()辺りで逃がすのは常套句なんですが、今回のケースだとmsgDirectiveのlink()で全要素に対してscrollがbindされていて、逃がす逃がさないの問題とすこしワケが違ったので、書き直しました。
ちなみに、挙動を確認したところ今回の例ではthrottle()は実は適していなくて、スクロールしているのに既読にならないメッセージと既読になるメッセージとバラつきがありました。あまり読まずにコメントしたものだから、すいません。
https://jsfiddle.net/h54sn6o7/5/
'throttle()'で逃さず全スクロールイベントを拾っているので、スクロールごとに処理は発生してますが、単純に、既読判定が不要なDirectiveはifで逃してます。この1行でパフォーマンスは4倍程度改善しました。

とのこと。

20
20
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?