隠れコンテンツのheightミスによるスクロール妨害のデモ
一部のマストドンでmp3動画(映像はマストドンが付加)を含む投稿の下の投稿に移動した後にmp3動画の投稿に戻れないバグがあったので、それに似せたデモ。実際の原因は別にあるが、同じ対処法が使えた。
下のコードでscroller内に見えなくなったarticleは内側のboxを削除し、articleタグにheightを記載する。scroller内に見えている場合はboxを生成し直し、articleタグにheightは削除される。各articleのheightはboxが存在する時に計算され170pxであるが、隠れてboxが存在しない場合も170pxが保たれる。ただし、「コンテンツ #2」だけは隠れている時のheightを修正できて、大きくすると、「コンテンツ #2」が隠れた後に「コンテンツ #2」を再表示しにくくなる。そのような時の対処法として「overflow-anchor: none;」がある。ただし、マストドンの方はスムーズにスクロールするが、このデモでは、揺れながらスクロールされる。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>スクロール妨害の再現と対処策のデモ</title>
<style>
/* スクロールコンテナ */
.scroller {
height: 350px;
overflow-y: scroll;
border: 2px solid #3498db;
position: relative;
}
/* リスト全体 */
.list-container {
width: 100%;
/* overflow-anchor の設定はJSで動的に行います */
}
/* 個別アイテム (articleタグ) */
.item {
padding: 15px;
box-sizing: border-box;
font-size: 18px;
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
/* コンテンツ内の四角いボックス */
.box {
width: 100px;
height: 100px;
margin-top: 10px;
background-color: #f39c12;
border: 2px solid #e67e22;
border-radius: 5px;
}
/* アイテムの色分け */
.item[data-index="1"] { background-color: #f7f3f9; border-left: 5px solid #8e44ad; }
.item[data-index="2"] { background-color: #e8f8f5; border-left: 5px solid #1abc9c; }
.item[data-index="3"] { background-color: #f9fcf2; border-left: 5px solid #2ecc71; }
.item[data-index="4"] { background-color: #f0f8ff; border-left: 5px solid #3498db; }
.item[data-index="5"] { background-color: #fdf6f0; border-left: 5px solid #e67e22; }
.item[data-index="6"] { background-color: #fceceb; border-left: 5px solid #e74c3c; }
/* スクロールステータス表示エリア */
#scrollStatus {
margin-top: 15px;
margin-left: 20px;
padding: 10px;
border: 1px solid #7f8c8d;
background-color: #ecf0f1;
font-family: monospace;
font-weight: bold;
}
#anchorControl {
margin-top: 10px;
padding: 10px;
border: 1px dashed #7f8c8d;
background-color: #f8f9fa;
}
input[type=checkbox] {
transform: scale(1.5);
}
</style>
</head>
<body>
<h1>スクロール妨害の再現と対処策のデモ</h1>
<div id="anchorControl">
<div>
<label for="heightInput">
コンテンツ #2 が隠れている時のheight (px):
<input type="number" id="heightInput" value="170" min="1" style="width: 80px;"><br>
(見えている時は170px。300pxに増やすと上へのスクロール妨害発生)
</label>
</div>
<div style="margin-top: 10px;">
<label for="anchorToggle">
<input type="checkbox" id="anchorToggle">
対処策:overflow-anchor: none; を有効にする
</label>
<span id="anchorStatus"></span>
</div>
</div>
<div class="scroller" id="scroller">
<div class="list-container" id="listContainer">
<article class="item" data-index="1">コンテンツ #1 <div class="box"></div></article>
<article class="item" data-index="2">コンテンツ #2 <div class="box"></div></article>
<article class="item" data-index="3">コンテンツ #3 <div class="box"></div></article>
<article class="item" data-index="4">コンテンツ #4 <div class="box"></div></article>
<article class="item" data-index="5">コンテンツ #5 <div class="box"></div></article>
<article class="item" data-index="6">コンテンツ #6 <div class="box"></div></article>
</div>
</div>
<div id="scrollStatus">
現在のスクロール位置 (scrollTop): 0.00 px
</div>
<script>
const SCROLL_CONTAINER = document.getElementById('scroller');
const LIST_CONTAINER = document.getElementById('listContainer');
const SCROLL_STATUS = document.getElementById('scrollStatus');
const ANCHOR_TOGGLE = document.getElementById('anchorToggle');
const ANCHOR_STATUS = document.getElementById('anchorStatus');
const HEIGHT_INPUT = document.getElementById('heightInput'); // 💡 高さ入力フィールド
const items = document.querySelectorAll('.item');
const itemHeightsCache = {};
// --- 0. スクロールアンカリング切替ロジック ---
function toggleAnchor() {
if (ANCHOR_TOGGLE.checked) {
LIST_CONTAINER.style.overflowAnchor = 'none';
ANCHOR_STATUS.textContent = '[現在: 有効]';
ANCHOR_STATUS.style.color = 'green';
} else {
LIST_CONTAINER.style.overflowAnchor = 'auto';
ANCHOR_STATUS.textContent = '[現在: 無効]';
ANCHOR_STATUS.style.color = 'red';
}
}
// --- 1. 初期化と高さ測定 ---
function initializeHeights() {
items.forEach(item => {
const measuredHeight = item.offsetHeight;
itemHeightsCache[item.dataset.index] = measuredHeight;
item.firstChild.textContent = `コンテンツ #${item.dataset.index} (実測高: ${measuredHeight}px)`;
});
toggleAnchor();
}
// --- 2. スクロールロジック ---
function applyScrollLogic() {
const scrollTop = SCROLL_CONTAINER.scrollTop;
const scrollerBottom = scrollTop + SCROLL_CONTAINER.offsetHeight;
// スクロール位置の表示を更新
SCROLL_STATUS.innerHTML = `現在のスクロール位置 (scrollTop): <strong>${scrollTop.toFixed(2)} px</strong>`;
items.forEach(item => {
const itemTop = item.offsetTop;
const itemIndex = item.dataset.index;
let cachedHeight = itemHeightsCache[itemIndex];
// --- #2 の固定高さをフォームの値から取得 ---
if (itemIndex === "2") {
// フォームから現在の高さを取得し、数値に変換
const inputHeight = parseInt(HEIGHT_INPUT.value);
cachedHeight = isNaN(inputHeight) || inputHeight < 1 ? itemHeightsCache[itemIndex] : inputHeight;
}
// --------------------------------------------------------
const currentHeight = item.style.height ? parseInt(item.style.height) : item.offsetHeight;
const isVisible = (itemTop < scrollerBottom) && (itemTop + currentHeight > scrollTop);
const box = item.querySelector('.box');
const fixedHeightStyle = `height: ${cachedHeight}px;`;
if (isVisible) {
// A. 画面内に表示されている場合 (height: auto)
item.removeAttribute('style');
if (!box) {
const newBox = document.createElement('div');
newBox.className = 'box';
item.appendChild(newBox);
}
} else {
// B. 画面外に隠れている場合 (上部/下部問わず height: 固定)
if (item.getAttribute('style') !== fixedHeightStyle) {
item.setAttribute('style', fixedHeightStyle);
}
if (box) {
box.remove();
}
}
});
}
// --- 3. イベントリスナーと実行 ---
SCROLL_CONTAINER.addEventListener('scroll', applyScrollLogic);
ANCHOR_TOGGLE.addEventListener('change', toggleAnchor);
// 💡 高さの入力値が変わったらロジックを再実行 (リフローさせる)
HEIGHT_INPUT.addEventListener('input', applyScrollLogic);
// 初期化処理を実行
window.onload = function() {
initializeHeights();
applyScrollLogic();
};
</script>
</body>
</html>
コメント
コメントを投稿