<div id="pageTop" class="js-smooth-scroll smooth-scroll">
<header class="header">
<h1>スムーズスクロール(Back to top含む)</h1>
<nav class="global-nav"></nav>
</header>
<main class="container main-wrapper">
<div class="section-wrap">
<section id="sectionA" class="section">
<h2 class="heading2">
スムーズスクロール(Smooth scroll)の挙動について - セクションA見出し
</h2>
<p>
スムーズスクロールという要素間アニメーション移動は、Webページにおけるコンテンツ移動の本来的な挙動からは外れたものということは知っておきたいところです。a要素の持つページ内遷移機能を疑似的に模してはいますが、それとは似て非なるものです。
</p>
<p>
そのためJavaScriptを用い、インタラクティブ要素として定義されたa要素の持つリンク遷移機能などのデフォルトアクションを<code class="language-javascript">preventDefault()</code>や<code class="language-javascript">return false</code>で停止したのちに、必要な処理設定をする手法が旧来から取られてきました。
</p>
<p>
このとき忘れられがちなのが、本来の挙動を強制的にキャンセルした結果、機能に抜けが発生したままになってしまうことです。フラグメント識別子(#~)をうんたらかんたらして、本来備えているキーボード操作等におけるフォーカス管理機能までが置き去りになっているケースがとても多いように見受けられます。
</p>
<p>
これはJavaScriptを用いて実装するインタラクティブなUIで、よく見られるケースです。スムーズスクロールであればこの点、<code class="language-javascript">HTMLElement.focus()</code>メソッドを使用したフォーカスコントロールは比較的容易です。
</p>
<p>
しかし見たことがないほど斬新で、離れ技のような動きや機能を実装すればするほど、本来持つべき機能を一度止めねばならず、また再実装で回復させることが困難になる傾向があります。
</p>
<p>
では一体これの何が問題なのでしょうか。HTMLを大もととするWebアプリケーションの機能はそもそもアクセシブル&ユーザブルに設計・策定されています。JSでのカスタマイズはその機能を殺してしまっているのです。
</p>
<p>
改めてWeb = ソフトウェアデザインにおける各UIが何のために生み出され、どういった役割を担うべきなのか、立ち返って見つめ直す必要があると強く感じます。スムーズスクロールはそのもっとも単純な例として良いサンプルになるでしょう。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
<section id="sectionB" class="section mt40">
<h2 class="heading2">
セクションB見出し
</h2>
<p>
セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。
</p>
<p>
セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
<section id="sectionC" class="section mt40">
<h2 class="heading2">
セクションC見出し
</h2>
<p>
セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。
</p>
<p>
セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
</div>
<aside class="aside">
<nav class="toc">
<div>目次</div>
<a href="#sectionA" class="toc__link">セクションA</a>
<a href="#sectionB" class="toc__link">セクションB</a>
<a href="#sectionC" class="toc__link">セクションC</a>
</nav>
</aside>
</main>
<footer class="footer">
<a href="#pageTop" class="back-to-top" title="ページの先頭(トップ)へ戻る" aria-label="ページの先頭(トップ)へ戻る">↑</a>
</footer>
</div>
<script src="../../js/vendor/prism.js"></script>
<div id="pageTop" class="js-smooth-scroll smooth-scroll">
<header class="header">
<h1>スムーズスクロール(Back to top含む)</h1>
<nav class="global-nav"></nav>
</header>
<main class="container main-wrapper">
<div class="section-wrap">
<section id="sectionA" class="section">
<h2 class="heading2">
スムーズスクロール(Smooth scroll)の挙動について - セクションA見出し
</h2>
<p>
スムーズスクロールという要素間アニメーション移動は、Webページにおけるコンテンツ移動の本来的な挙動からは外れたものということは知っておきたいところです。a要素の持つページ内遷移機能を疑似的に模してはいますが、それとは似て非なるものです。
</p>
<p>
そのためJavaScriptを用い、インタラクティブ要素として定義されたa要素の持つリンク遷移機能などのデフォルトアクションを<code class="language-javascript">preventDefault()</code>や<code class="language-javascript">return false</code>で停止したのちに、必要な処理設定をする手法が旧来から取られてきました。
</p>
<p>
このとき忘れられがちなのが、本来の挙動を強制的にキャンセルした結果、機能に抜けが発生したままになってしまうことです。フラグメント識別子(#~)をうんたらかんたらして、本来備えているキーボード操作等におけるフォーカス管理機能までが置き去りになっているケースがとても多いように見受けられます。
</p>
<p>
これはJavaScriptを用いて実装するインタラクティブなUIで、よく見られるケースです。スムーズスクロールであればこの点、<code class="language-javascript">HTMLElement.focus()</code>メソッドを使用したフォーカスコントロールは比較的容易です。
</p>
<p>
しかし見たことがないほど斬新で、離れ技のような動きや機能を実装すればするほど、本来持つべき機能を一度止めねばならず、また再実装で回復させることが困難になる傾向があります。
</p>
<p>
では一体これの何が問題なのでしょうか。HTMLを大もととするWebアプリケーションの機能はそもそもアクセシブル&ユーザブルに設計・策定されています。JSでのカスタマイズはその機能を殺してしまっているのです。
</p>
<p>
改めてWeb = ソフトウェアデザインにおける各UIが何のために生み出され、どういった役割を担うべきなのか、立ち返って見つめ直す必要があると強く感じます。スムーズスクロールはそのもっとも単純な例として良いサンプルになるでしょう。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
<section id="sectionB" class="section mt40">
<h2 class="heading2">
セクションB見出し
</h2>
<p>
セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。
</p>
<p>
セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。セクションBコンテンツ。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
<section id="sectionC" class="section mt40">
<h2 class="heading2">
セクションC見出し
</h2>
<p>
セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。
</p>
<p>
セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。セクションCコンテンツ。
</p>
<a href="#" class="mt24">ダミーリンクです</a>
</section>
</div>
<aside class="aside">
<nav class="toc">
<div>目次</div>
<a href="#sectionA" class="toc__link">セクションA</a>
<a href="#sectionB" class="toc__link">セクションB</a>
<a href="#sectionC" class="toc__link">セクションC</a>
</nav>
</aside>
</main>
<footer class="footer">
<a
href="#pageTop"
class="back-to-top"
title="ページの先頭(トップ)へ戻る"
aria-label="ページの先頭(トップ)へ戻る"
>↑</a
>
</footer>
</div>
<script src="../../js/vendor/prism.js"></script>
/* No context defined. */
/* /js/modules/SmoothScroll.js */
export function scrollSmooth() {
// スクロール移動時間(ms)
const transitionTime = 500;
// 繰り返す回数(アニメーションの回数)の計算
const endTime = transitionTime / 1000 * 60;
// easing計算式 -> easeInOutQuad
const easing = function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
};
// 移動関数(スムーズアニメーション実行関数)
function pageScroll(pos) {
let xPos = window.pageXOffset; // 画面横位置
let yPos = window.pageYOffset; // 画面縦位置
let moved = pos - yPos; // 移動距離
let time = 1;
(function scrollMoved() {
window.scrollTo(xPos, easing(time, yPos, moved, endTime));
time++ ;
if (time <= endTime) {
window.requestAnimationFrame(scrollMoved);
}
})();
}
// hashからelementを取得する関数
function getElm(h) {
let deCode = decodeURI(h);
return document.querySelector( deCode + ',a[name="' + deCode.substr(1) + '"]');
}
// 移動先の位置を取得する関数
function getPos(t) {
let tPos = t.getBoundingClientRect();
return tPos.top + window.pageYOffset;
}
// ページ内リンクを取得
const entryPageLinks = document.querySelectorAll('.js-smooth-scroll a[href^="#"]');
const toc = document.querySelectorAll('.toc__link');
const section = document.querySelectorAll('.section');
// ページ内リンクにイベントを登録
if (entryPageLinks.length) {
for(var i = 0; entryPageLinks.length > i; i++) {
entryPageLinks[i].addEventListener('click', (e) => {
let href = e.target.hash;
if ( (href != '') && (href != '#')) {
let targetElm = getElm(href);
if (targetElm) {
(e.preventDefault) ? e.preventDefault(): e.returnValue = false;
pageScroll(getPos(targetElm));
targetElm.setAttribute('tabindex', '0');
targetElm.focus();
targetElm.addEventListener('blur', () => {
targetElm.removeAttribute('tabindex');
});
for(let i = 0; toc.length > i; i++) {
if (href === toc[i].hash) {
toc[i].classList.add('is-current');
} else {
toc[i].classList.remove('is-current');
}
}
for(let j = 0; section.length > j; j++) {
if (targetElm !== section[i]) {
section[j].removeAttribute('tabindex');
}
}
return false;
}
}
});
}
}
}
/* /js/modules/CurrentLocation.js */
/* 暫定コード */
export function currentLocation() {
const root = document.querySelector('body');
const boxes = document.querySelectorAll('.section');
const options = {
root: root ? null : null,
rootMargin: '-1px 0px -70% 0px',
threshold: 0.2
};
const observer = new IntersectionObserver(doWhenIntersect, options);
// それぞれのboxを監視する
boxes.forEach(box => {
observer.observe(box);
});
/**
* 監視している要素が交差したとき呼び出す関数
* @param entries
*/
function doWhenIntersect(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
activateIndex(entry.target);
}
});
}
/**
* 目次上見出しの現在地classを付与する関数
* @param element
*/
function activateIndex(element) {
// .is-currentが付与されている目次を選択
const currentActiveIndex = document.querySelector('.toc .is-current');
// .is-currentが付与されているものが0個の時(=null)以外は.is-currentを除去
if (currentActiveIndex !== null) {
currentActiveIndex.classList.remove('is-current');
}
// 引数で渡されたDOMが飛び先のaタグを選択し、.is-currentを付与
const newActiveIndex = document.querySelector(
`a[href='#${element.id}']`
);
newActiveIndex.classList.add('is-current');
}
}
/* /js/main.js */
import { currentLocation } from './modules/CurrentLocation.js';
import { scrollSmooth } from './modules/SmoothScroll.js';
currentLocation();
scrollSmooth();
@media all and (-ms-high-contrast: none) {
.smooth-scroll *::-ms-backdrop,
.smooth-scroll .container {
max-width: none;
}
}
.smooth-scroll .header {
position: relative;
padding-top: 3rem;
padding-bottom: 2rem;
background-color: #eee;
text-align: center;
font-size: 1rem;
box-shadow: 0 1px 10px rgba(0, 0, 0, .35);
}
.smooth-scroll .container {
display: flex;
width: 100%;
max-width: 80rem;
}
@media print, screen and (min-width:64.0625em) {
.smooth-scroll .container {
gap: 2rem;
}
}
@media print, screen and (min-width:48em) {
.smooth-scroll .container {
flex-direction: row;
justify-content: flex-start;
}
}
@media print, screen and (max-width:64em) {
.smooth-scroll .container {
flex-direction: column-reverse;
justify-content: flex-start;
width: 90vw;
}
}
.smooth-scroll .section-wrap {
display: flex;
flex-direction: column;
width: auto;
height: 80%;
}
.smooth-scroll .section {
list-style-type: none;
width: auto;
height: 100%;
font-size: 1rem;
}
@media print, screen and (min-width:48em) {
.smooth-scroll .section {
padding: 3.5rem 4rem;
}
}
.smooth-scroll .section p {
margin-bottom: 1rem;
}
.smooth-scroll .section a {
display: inline-block;
}
.smooth-scroll .section:nth-of-type(3) {
margin-bottom: 20rem;
}
.smooth-scroll .section:focus {
outline: none;
}
.smooth-scroll .aside {
min-width: 20%;
}
@media print, screen and (max-width:64em) {
.smooth-scroll .aside {
margin-bottom: 2rem;
}
}
.smooth-scroll .toc {
display: flex;
flex-direction: column;
position: -webkit-sticky;
position: sticky;
top: 2rem;
width: 100%;
text-align: center;
line-height: 3;
}
.smooth-scroll .toc__link {
padding: .5rem;
transition: all .25s;
}
@media print, screen and (max-width:64em) {
.smooth-scroll .toc__link,
.smooth-scroll .toc__link:hover,
.smooth-scroll .toc__link:focus {
text-decoration: underline;
font-weight: normal;
}
}
@media print, screen and (min-width:64.0625em) {
.smooth-scroll .toc__link:hover,
.smooth-scroll .toc__link:focus {
text-decoration: none;
font-weight: bold;
}
.smooth-scroll .toc__link.is-current {
position: relative;
background-color: #ccf4ff;
transition: all .5s;
}
.smooth-scroll .toc__link.is-current:before {
content: "";
display: inline-block;
position: absolute;
top: 50%;
left: 2.75em;
width: 1em;
height: .1rem;
background-color: #00e;
}
}
.smooth-scroll .footer {
position: relative;
height: 15rem;
padding-top: 6rem;
background-color: #eee;
text-align: center;
font-size: 3rem;
}
.smooth-scroll .back-to-top {
align-items: center;
justify-content: center;
position: absolute;
top: -3rem;
right: 8%;
width: 6rem;
height: 3rem;
padding-top: .5rem;
background-color: #ccc;
line-height: 1.5rem;
font-size: 1.5rem;
font-weight: bold;
border-radius: 5rem 5rem 0 0;
}
.smooth-scroll .footer + div {
left: 3rem;
width: 20rem;
}
No notes defined.