2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリザンターにスクロールボタンを追加してみる

2
Posted at

はじめに

Webサイトでよく見かける、画面右下に浮かぶ「上へ」「下へ」ボタン。レコード数が多い一覧画面や入力項目が多い編集画面では、ページの先頭や末尾にすばやく移動できると便利です。

この記事では、拡張スクリプトと拡張スタイルで画面右下にフローティングボタンを追加する方法を紹介します。

仕組みを整理する

実装のポイントは次のとおりです。

項目 内容
実行条件 $p.controller === 'items' のときのみ動作
ボタンの配置 position: fixed で画面右下に固定。十字パッド型のレイアウト
アイコン Material Symbols(arrow_upwardarrow_downwardarrow_backarrow_forward
スクロール対象 $p.action === 'index' なら Shadow DOM 内 .app-grid-framewindow、その他は window のみ
ボタン構成 一覧画面は上下左右 4 方向、その他は上下 2 方向。すべて常時表示

実装してみよう

拡張スクリプトと拡張スタイルの 2 ファイルで構成します。

拡張機能 役割
拡張スタイル ボタンの見た目と配置を定義
拡張スクリプト ボタンの生成・スクロール処理

ボタンのスタイル(拡張スタイル)

拡張スタイルとして App_Data/Parameters/ExtendedStyles/ に配置します。

ExtendedStyles/ScrollButtons.css
/* --- 十字パッド型コンテナ --- */
.scroll-pad {
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: 84px;
  height: 84px;
  z-index: 900;
}

/* --- 共通ボタンスタイル --- */
.scroll-pad button {
  position: absolute;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background: #455a64;
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  transition: opacity 0.2s, background 0.2s;
  opacity: 0;
  pointer-events: none;
  padding: 0;
}

.scroll-pad button .material-symbols-outlined {
  font-size: 20px;
}

.scroll-pad button.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.scroll-pad button:hover {
  background: #37474f;
}

/* --- 各ボタンの配置(十字型) --- */
.scroll-pad .sp-up {
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

.scroll-pad .sp-down {
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.scroll-pad .sp-left {
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

.scroll-pad .sp-right {
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

/* --- 中央の装飾円 --- */
.scroll-pad::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(69, 90, 100, 0.3);
  pointer-events: none;
}

/* --- window スクロール時はコンパクトに --- */
.scroll-pad.sp-vertical-only {
  width: 36px;
  height: 84px;
}

.scroll-pad.sp-vertical-only::after {
  display: none;
}

.scroll-pad.sp-vertical-only .sp-up {
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

.scroll-pad.sp-vertical-only .sp-down {
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

ポイントをまとめます。

  • .scroll-padposition: fixed で画面右下に固定
  • 4 つのボタンを position: absolute で十字型に配置。中央に小さな装飾円を置き、ゲームパッド風の見た目に
  • window スクロールの画面では .sp-vertical-only クラスを付け、上下のみのコンパクトなレイアウトに切り替え
  • ボタンは初期状態で opacity: 0 かつ pointer-events: none(非表示・クリック無効)
  • .is-visible クラスが付くと opacity: 1 に変わり表示される
  • transition でふわっと表示・非表示が切り替わる
  • z-index: 900 でプリザンターの標準UIと干渉しない程度の前面に配置

ボタンの生成とスクロール処理(拡張スクリプト)

拡張スクリプトとして App_Data/Parameters/ExtendedScripts/ に配置します。

ExtendedScripts/ScrollButtons.js
$(function () {
  // items コントローラーのみ対象
  if ($p.controller() !== 'items') return;

  var isIndex = $p.action() === 'index';

  if (isIndex) {
    // カスタム要素のアップグレード完了を待ってから初期化
    customElements.whenDefined('grid-container').then(function () {
      var gridEl = document.querySelector('grid-container[data-scrollable]');
      var frame = gridEl && gridEl.shadowRoot
        ? gridEl.shadowRoot.querySelector('.app-grid-frame')
        : null;
      init(frame);
    });
  } else {
    init(null);
  }

  function init(frame) {
    var useGrid = Boolean(frame);

    // 十字パッドコンテナを生成
    var $pad = $('<div>', {
      class: 'scroll-pad' + (useGrid ? '' : ' sp-vertical-only')
    });

    // ボタン生成ヘルパー
    function createBtn(css, icon, title) {
      return $('<button>', { type: 'button', class: css, title: title })
        .append($('<span>', {
          class: 'material-symbols-outlined',
          text: icon
        }));
    }

    // 「上へ」
    var $up = createBtn('sp-up is-visible', 'arrow_upward', '先頭へ');
    $up.on('click', function () {
      if (useGrid) frame.scrollTo({ top: 0, behavior: 'smooth' });
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    // 「下へ」
    var $down = createBtn('sp-down is-visible', 'arrow_downward', '末尾へ');
    $down.on('click', function () {
      if (useGrid) {
        frame.scrollTo({ top: frame.scrollHeight, behavior: 'smooth' });
      }
      window.scrollTo({
        top: document.documentElement.scrollHeight,
        behavior: 'smooth'
      });
    });

    // DOM に追加
    $pad.append($up).append($down);

    // 「左へ」「右へ」(一覧画面のみ)
    if (useGrid) {
      var $left = createBtn('sp-left is-visible', 'arrow_back', '左端へ');
      $left.on('click', function () {
        frame.scrollTo({ left: 0, behavior: 'smooth' });
      });

      var $right = createBtn('sp-right is-visible', 'arrow_forward', '右端へ');
      $right.on('click', function () {
        frame.scrollTo({ left: frame.scrollWidth, behavior: 'smooth' });
      });

      $pad.append($left).append($right);
    }

    $('body').append($pad);
  }
});

各処理のポイントを見ていきましょう。

実行条件とモード分岐

$p.controller$p.action で動作を切り替えます。

条件 動作
$p.controller !== 'items' 何もしない(管理画面等ではボタン不要)
$p.action === 'index' Shadow DOM 内 .app-grid-framewindow をスクロール→ 上下左右 4 方向
その他(editnewkamban 等) window のみスクロール → 上下 2 方向

すべてのボタンは常時表示です。スクロール位置による表示・非表示の切り替えは行わず、いつでもクリックできます。

カスタム要素の初期化待ち

一覧画面($p.action === 'index')では customElements.whenDefined('grid-container') でカスタム要素のアップグレード完了を待ちます。

プリザンターの <grid-container> はカスタム要素(Web Components)として実装されており、$(function(){...})(DOMContentLoaded)の時点ではまだアップグレードが完了していない場合があります。アップグレード前は shadowRootnull のため、whenDefined() で確実に利用できるタイミングまで待ちます。

Shadow DOM の構造

プリザンターの <grid-container>data-scrollable 属性があるとき Shadow DOM(mode: 'open')を使い、内部に次の構造を持ちます。

<grid-container data-scrollable="1">
  #shadow-root (open)
    ├─ .app-grid-inner
    │   ├─ .app-grid-frame   ← 実際のスクロールコンテナ(overflow: auto)
    │   │   └─ <slot>         ← Light DOM の .grid テーブルが投影
    │   └─ .app-scroll-layer

<grid-container> 要素自体には overflow が設定されておらず、scrollTo()scrollTop を呼んでも効果がありません。Shadow DOM 内の .app-grid-frameoverflow: auto を持つ実際のスクロールコンテナなので、shadowRoot.querySelector('.app-grid-frame') で取得して操作します。

Material Symbols の arrow_upward 等のアイコンを使います。プリザンターでは Google Material Symbols がすでに読み込まれているので、追加のフォント読み込みは不要です。

スクロール処理

一覧画面では Shadow DOM 内の .app-grid-framewindow の両方、その他の画面では window のみをスクロールします。左右ボタンは frame.scrollLeft / frame.scrollWidth を使って水平方向にスクロールします。どの方向も behavior: 'smooth' でスムーズに動作します。

カスタマイズ

ボタンの見た目や動作は CSS と JavaScript の値を変えるだけで調整できます。

ボタンの色を変える

ExtendedStyles/ScrollButtons.css
 .scroll-pad button {
-  background: #455a64;
+  background: #333;
 }

 .scroll-pad button:hover {
-  background: #37474f;
+  background: #555;
 }

ボタンの位置を変える

ExtendedStyles/ScrollButtons.css
 .scroll-pad {
   right: 24px;
-  bottom: 24px;
+  bottom: 80px;
 }

表示例

一覧画面

image.png

それ以外(カレンダーやカンバンなど)

上下.png

まとめ

プリザンターの画面に十字パッド型のスクロールボタンを拡張スクリプトと拡張スタイルだけで追加しました。

  • $p.controller === 'items' のときのみ動作
  • $p.action === 'index' なら Shadow DOM 内 .app-grid-frame をスクロールし上下左右 4 方向、その他は window スクロールで上下 2 方向
  • customElements.whenDefined() でカスタム要素の初期化完了を待ち、Shadow DOM を確実に取得
  • ボタンはすべて常時表示。スクロール位置による出し分けは行わない
  • position: fixed で画面右下に十字パッドを固定配置
  • Material Symbols の矢印アイコンを使用
  • 拡張スタイルと拡張スクリプトの 2 ファイルだけで動作
2
0
0

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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?