ユーザーのページ変更操作を知る 

ユーザーが閲覧しているアメーバのブログページをページャー等を押して移動した時、この「ページが変更された」事を知る方法は幾つか考えられると思います。

 

例えば「URL窓」の内容はページ移動で変化しますから、これを取得してページが変更された事を察知できるはずです。

 

 

こういったコードの作成は、これまで余り深く考えていなかった問題でした。

 

 

 

「MutationObserver」は万能ではない 

ページ上の要素の変化に合わせて動作するプログラムを作るのに、一番使えるメソッドは「MutationObserver」です。 監視ターゲットを指定し、検知する変化の種類、検知時に動作させる関数などを指定するメソッドです。 私の制作したツールの殆どがこの機能を使っています。

 

「MutationObserver」の動作を調べると、一般に非常に「敏感」に変化に反応するので、乱用するとブラウザのリソースを食い尽くしかねない気がします。「目的にあった感度」を設定してやる必要を感じるのです。

 

しかし、それでも「MutationObserver」は働き過ぎます。 今回、ブログページを開いた時に、記事の本文をウインドウの中央に寄せるツールを制作していて、この問題に突き当たりました。

 

 

通常は使い易いターゲット「head」 

ブログページの変更を「MutationObserver」を使って検知する場合、監視するターゲットに「head」(HTMLのヘッダー)、検知の種類を「childList」(子要素の数)とすると、確実にページの変更を知る事ができます。 これは、ページによって「head」内部の構成が違うからで、ページ固有のDNAを調べている様なものです。

 

 

通常は、ページャーで移動した時に変化する下の赤枠部分の要素を監視ターゲットにすれば良さそうに思います。

 

 

実際、これでスクリプトは機能しますが、「記事一覧」を表示したり「ブログトップ」へ移動した後は、スクリプトが止まってしまいます。 こういった試行錯誤の結果、「head」をターゲットにする事になったのです。

 

 

何度も検知している 

「MutationObserver」で「head」の変化をチェックさせると、ページを開いてから10を超える程度の回数の検知を行っている事が判ります。しかし、その1回目の検知で目的の関数が起動し、表面上は「うまく関数が動作した」と判断されます。

 

着火機能としての「MutationObserver」はその様に動作して、通常はそれが問題にはならないのですが、場合によっては困った事になります。

 

「記事本文をウインドウ中央に寄せるツール」の原型を制作していて、「寄せる」機能として最初に選択したのは「scrollIntoView」のメソッドです。 これは、目的の要素をウインドウの中央や、先頭部などに自動的に配置表示する便利なメソッドです。

 

記事の本文部分は、スキンの種類にかかわらず「#main」という要素に収められていて、これをウインドウ中央に表示するには、

 

let blog_main=document.querySelector('#main');
if(blog_main){
    blog_main.scrollIntoView({ inline: "center"}); }

 

といった、簡単なコードで実現できます。

 

しかし、このコードを先の「MutationObserver」を使って、ページを開いた時に実行させるコードを作ったところ、「使えない」と言うことが判りました。 下が試作コードです。

 

// ==UserScript==
// @name         Blog Centering
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  ブログ記事本文をWindowの中央に表示する
// @author       Ameba Blog User
// @match        https://ameblo.jp/*
// @noframes
// @grant        none
// ==/UserScript==


let target=document.querySelector('head'); // 監視 target
let monitor=new MutationObserver(do_center);
monitor.observe(target, { childList: true }); // 監視開始

do_center();

function do_center(){
    let blog_main=document.querySelector('#main');
    if(blog_main){
        blog_main.scrollIntoView({ inline: "center"}); }}

 

 

実際に動作させてみると判りますが、ページを開くと「ページャー」を上部の先頭として、本文記事が中央に表示されます。

 

❶「ページャー」は「#main」の先頭要素ですから、アメブロヘッダーに一部が隠れるかもしれませんが、window上端に「#main」が接して表示されます。

 

❷「inline: "center"」の指定は、横方向のスクロール位置を、指定した要素を中央に表示する様に働きます。 ウインドウに横スクロールバーが表示されない場合、つまり充分にウインドウ幅が広い場合は、この指定の効果は判りません。 しかし、ウインドウ幅を狭めてページを開きなおすと、ウインドウ中央に本文を表示する機能が働いている事が判ります。

 

問題というのは、ページを開いた最初に縦方向のスクロールをすると判ります。 スクロール操作を邪魔する様に、何度も最初の「ページャー」からの表示に戻されます。

 

 

試作コードの問題 

この様な事が生じるのは、「MutationObserver」が何度も動作しているからです。 おそらくページ下方にスクロールすると、画像の「Lazy Load」の読み込みが生じて「head」内部が書換えられるからでしょう。 その度に検知が働き「scrollIntoView」が動作しています。

 

「scrollIntoView」のメソッドは、対象要素をスクロール位置に表示した後は終了して、後はユーザーの自由に任せます。 しかし上記コードは何度も「scrollIntoView」を起動しているのです。

 

邪魔を押してなんとか下部までスクロール表示すると、「head」の書換えが無くなり、その後は自然にスクロールが可能になります。 でも、これでは使えません。

 

 

ページの変更で一度だけ動作するコード 

「MutationObserver」で「URL」の変化を調べさせる事ができればと思い、次のコードを試しました。

 

let target=location.pathname; // 監視 target
let monitor=new MutationObserver(do_center);
monitor.observe(target, { childList: true }); // 監視開始

 

あきません ^^;

「'MutationObserver': parameter 1 is not of type 'Node'」とエラーが出ます。 そらそうです。 ページの「URL」や「パス名」は「要素」ではないですから。

 

で、「パス名」はページ変更の度に1回しか変化せず、これを「MutationObserver」に頼らずに調べるコードを作れば良いわけです。 ちょっと頭を捻って下のコードを作りました。 ネット上に載っていそうなコードですが。

 

 

// ==UserScript==
// @name         Blog Centering
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  ブログ記事本文をWindowの中央に表示する
// @author       Ameba Blog User
// @match        https://ameblo.jp/*
// @noframes
// @grant        none
// ==/UserScript==


let test_arr=[0, 0];

let target=document.querySelector('head'); // 監視 target
let monitor=new MutationObserver(do_center);
monitor.observe(target, { childList: true }); // 監視開始

do_center();

function do_center(){
    let page= location.pathname;
    test_arr.push(page);
    test_arr.shift();

    if(test_arr[0]!=test_arr[1]){
        let blog_main=document.querySelector('#main');
        if(blog_main){
            blog_main.scrollIntoView({ inline: "center"}); }}}

 

 

今度は、上手く行きました。 スクロールが邪魔される事はありません。

 

上記の「do_center()」関数の特徴は、中身が2個だけの配列「test_arr」を使った事です。 配列に対して「push」メソッドは後ろに次の配列の子(なぜか適当な呼び名がないですね)を追加します。 また「shift」メソッドは先頭の配列の子を削除します。

 

これを使って、配列の先頭「test_arr[0]」と末尾の「test_arr[1]」がバケツリレーの様に、後ろから新しく取得した「パス名」を送って行く動作をします。

 

この関数が動作する度に現在の「パス名」を取得して、前回に取得した「パス名」は配列の先頭に移動させるので、配列の2個を比較すれば「パス名」が変化した時を判断できます。

 

この「do_center()」関数自体は、「MutationObserver」でページを開いてから何度も起動されますが、「パス名」が変わらない限り最後の「scrollIntoView」を動作させないという塩梅です。

 

 

このツールでは不満足 

かなり動作が改善されましたが、未だ不満足です。 それは先の ❶の動作の問題です。

 

「scrollIntoView」は、対象要素を簡単に指定した配置に表示する便利な機能ですが、縦方向のスクロール配置がキャンセルできません。 これは今回初めて判った事ですが、ネット上にも余り問題にされていない様です。

 

blog_main.scrollIntoView({ inline: "center"});

 

のコードでは「inline: "center"」の横方向の中央に表示する指定をしていますが、この指定で勝手に「block: "center"」(縦方向の初期値)が指定されてしまいます。 

「scrollIntoView」を使う場合は、殆どが縦方向のスクロール配置を目的とするので、それが要らないというのは仕様の策定時に考えなかった様です。

 

このツールの目的は、記事本文の左右配置のみをできるだけ中央に寄せ、上下のスクロール配置は、ページの先頭から表示させたいのです。 ブログヘッダー部が全く隠れたり、ページャーが隠れてしまうのは、望むデザインではないわけです。

 

このツールがスマートに動作できるなら、「Remember My Page」に実装したいと思っています。「Remember My Page」は、ページの自動拡大で小さな文字のブログを拡大して見易くできますが、この拡大でブログを開く度に横スクロールしないと本文記事が読めないページがあるのです。 これを改善したいというのが、そもそもの始まりなのです。

 

横方向の配置を「scrollIntoView」に頼っていては駄目かなと思っています。