CSSとJavaScriptで横スクロールするお知らせ画面を作成する
前回の「CSSで円形の上下左右矢印アイコンを描く例」の続きみたいなものですが、せっかく左右の矢印アイコンを作ったので、それを活用して、矢印をクリックしたら横スクロールするお知らせ画面を作ってみたいと思います。
横スクロールするお知らせ画面
お知らせ画面が横スクロールすると言われても言葉だけだとピンと来ないかと思うので、まずは表示サンプル。
表示例
左右にある矢印アイコンをクリックすることお知らせのメッセージがいいかんじにスクロールするハズです。
短いテキストと長いテキストを交互に表示していますが、どちらも、矢印アイコンクリック後に真ん中付近にピタっとスクロールが止まるところがポイント。
いわゆるスナップスクロールという機能を実装しています。
ちなみにスマホ対応もしているので、スマホの場合は矢印だけではなく、タッチ操作で横スクロールも可能。
HTML部分
<div class="hscroll_wrapper">
<span class="arrow left"></span>
<div>
<a href="">短い文字1</a>
<a href="">長めのお知らせテキスト1</a>
<a href="">短い文字2</a>
<a href="">長めのお知らせテキスト2</a>
<a href="">短い文字3</a>
<a href="">長めのお知らせテキスト3</a>
</div>
<span class="arrow right"></span>
</div>
特に複雑なことはしていませんが、全体をdiv要素(hscroll_wrapper)で囲み、その中に矢印アイコン(arrow left/arrow right)とdiv要素で囲ったaタグを並べているだけです。
CSS部分
.hscroll_wrapper {
position:relative;
padding:0 1.5em 0 0.5em;
border:1px solid #cccccc;
border-radius:0.5em;
max-width:300px;
}
.hscroll_wrapper div {
white-space:nowrap;
overflow-x:hidden;
margin: 0 0.1em 0 1em;
scroll-behavior:smooth;
scroll-snap-type:inline;
}
.hscroll_wrapper a {
display:inline-block;
margin:0 1em 0 0;
scroll-snap-align:center;
}
@media screen and (max-width:480px) {
.hscroll_wrapper div {
overflow-x:auto;
}
}
CSSの解説
矢印アイコンのCSSについては前回の記事「CSSで円形の上下左右矢印アイコンを描く例」で詳しく解説しているため端折ります。
hscroll_wrapperクラス
お知らせの角丸枠線を描く部分です。スナップスクロールの吸着具合がわかりやすいようにmax-width:300pxと幅を短めにしていますが、別に幅100%指定でも問題ありません。
このクラスで大事なのはposition:relative;だけですね。このhscroll_wrapperクラスの中に入れる矢印アイコンは相対座標指定したいのでrelativeが必須となります。
hscroll_wrapperクラスの中のdiv要素
このdiv要素が「横スクロールするテキスト」が描画される範囲になります。言い換えると、左右矢印アイコンに挟まれた範囲、とも言える部分。
この中に書かれるテキストは折返し表示しないようにwhite-space:nowrap;を指定。
また、この範囲からはみ出したテキストを非表示にするため、overflow-x:hidden;も指定しています。
そして、今回の目玉(?)というか、ちょっと「いいかんじ」の横スクロールにしたかったため指定しているこの2つ。
scroll-behavior:smooth;
scroll-snap-type:inline;
scroll-behavior:smooth;は名前からも想像できるとおり、スクロールの描画をぬるっとスムースにするためのプロパティ。
もうひとつのscroll-snap-typeは、スクロール時、「いいかんじ」に要素をスナップ(吸着)して表示するプロパティです。これが指定されているから、短いテキストと長いテキストが交互に表示されていても、どちらもピタッと真ん中付近に止まるんですね。まぁ、「真ん中付近」という指定はまた別のプロパティなのですが。
hscroll_wrapperクラスの中のa要素
ここで指定しているのが吸着時の表示方法、scroll-snap-align:center;です。これを指定することで、真ん中付近にお知らせのテキストが表示される仕組みになっています。左端や右端に表示したい場合はleftやright…………ではなく、startやendを指定します。
例:scroll-snap-align:start;
だって、スクロールって横スクロールだけじゃないもんね。むしろ縦スクロールのほうが一般的だろうし、そのときにscroll-snap-align:left;じゃ変だもの。
@media screenでスマホ対応
@media screen and (max-width:480px) { ~ } で囲まれた中のCSS指定はスマホ(画面の横幅が短い)時の指定です。
ここではoverflow-x:hidden;をauto;へ切り替えています。どういうことかというと、PCのブラウザではスクロールバーが太くてダサく、デザインも変更できないため、hiddenで消しています。
しかしスマホの場合はいちいち矢印アイコンをタップするよりも、指でスワイプ操作するほうが慣れている方も多いでしょうし、スクロールバーも細くて邪魔にならないため、overflow-x:auto;を指定してスクロールバーを表示しているわけです。
PCでChromeもしくはEdge Chromiumをお使いならメニューの「その他のツール」から「デベロッパーツール」(Edgeでは開発者ツール)を選ぶか、Ctrl+Shift+Iで開発者ツール画面を表示すればスマホモードでの表示も確認できます。
JavaScriptの解説
document.querySelectorAll('.left').forEach(elm => {
elm.onclick = function () {
let div = this.parentNode.querySelector('.hscroll_wrapper div');
div.scrollLeft -= (div.clientWidth / 2);
};
});
document.querySelectorAll('.right').forEach(elm => {
elm.onclick = function () {
let div = this.parentNode.querySelector('.hscroll_wrapper div');
div.scrollLeft += (div.clientWidth / 2);
};
});
矢印アイコンをクリックした際はJavaScriptで横スクロールを実現していますが、ここは意外と単純な仕組みです。
まず、querySelectorAll('.left').forEach( ~ ) はクラス名leftを持つ要素をリストアップするコードで、その中でonclickイベントを設定しています。
(.rightも同様)
onclickイベントではそのイベントが発生した要素の親要素から、「.hscroll_wrapper div」に一致する要素を検索し、その要素をスクロールさせています。
なぜこんなややこしいことをしているのかと言うと、この横スクロールを同一ページ内に複数配置する可能性があるからです。
id固定で要素を探せばもっとシンプルなコードになりますが、それでは横スクロール画面を複数貼り付けた際に、その数分のidをふらなければいけません。これは面倒だしメンテナンス性も悪い。
ということで、onclickイベントが発生した要素の親要素から云々、という探し方をしています。
そして、スクロールのキモとなる部分、div.scrollLeft -= (div.clientWidth / 2);ですが、スクロール範囲内の横幅(clientWidth)の半分の長さだけスクロールするという動作をしています。(rightの場合は逆に+=にしている)
でも実際動かしてみればわかりますが、必ず半分動くというわけではありません。お知らせ画面の半分「付近」にあるテキストにいいかんじに吸着して止まるハズです。
CSSでscroll-snap-type:inline;とscroll-snap-align:center;を指定しているおかげですね。
この実装はなんとなく中途半端な感じもします。例えばお知らせテキストはすべてa要素で書かれているのだから、内部的に番号でもふって、矢印アイコンをクリックするたびに増減させ、順番どおりにテキストが並ぶようにしたほうがより自然かも知れません。
でも、スマホ対応の項目で解説したとおり、これはタッチ操作にも対応しているのです。
だから、内部的にお知らせ番号的なモノを持っていたとしてもタッチ操作でスクロールしたら意味がありません。
やるとしたら、hscroll_wrapperのブラウザ内座標を保存し、その中のa要素のX座標を取得、その後、相対座標を見て、真ん中より右側にあるa要素を真ん中に持ってくる…という大変面倒な実装になります。
そんなクソ面倒くさいことするくらいなら、お知らせ枠の半分だけスクロールして吸着具合はCSSに任せる、ほうがずいぶんスマートじゃないですかね。
HTMLの全コード
HTMLタグ、CSS、JavaScriptと部分ごとに解説してきましたが、最後に全体像も掲載しておきます。
<div class="hscroll_wrapper">
<span class="arrow left"></span>
<div>
<a href="">短い文字1</a>
<a href="">長めのお知らせテキスト1</a>
<a href="">短い文字2</a>
<a href="">長めのお知らせテキスト2</a>
<a href="">短い文字3</a>
<a href="">長めのお知らせテキスト3</a>
</div>
<span class="arrow right"></span>
</div>
<style>
.hscroll_wrapper {
position:relative;
padding:0 1.5em 0 0.5em;
border:1px solid #cccccc;
border-radius:0.5em;
max-width:400px;
}
.hscroll_wrapper div {
white-space:nowrap;
overflow-x:hidden;
margin: 0 0.1em 0 1em;
scroll-behavior: smooth;
scroll-snap-type:inline;
}
.hscroll_wrapper a {
display:inline-block;
margin:0 1em 0 0;
scroll-snap-align:center;
}
@media screen and (max-width:480px) {
.hscroll_wrapper div {
overflow-x:auto;
}
}
.arrow {
display:inline-block;
position:absolute;
border-radius:50%;
width:1em;
height:1em;
cursor:pointer;
border:1px solid #808080;
vertical-align:middle;
margin-bottom:4px;
top:3px;
}
.left {
left:2px;
}
.right {
right:2px;
}
.arrow:before {
content:'';
position:absolute;
width:0.4em;
height:0.4em;
border-left:1px solid #808080;
border-bottom:1px solid #808080;
}
.left:before {
top:28%;
left:35%;
transform:rotate(45deg);
}
.right:before {
top:28%;
left:20%;
transform:rotate(-135deg);
}
.arrow:hover {
background-color:#808080;
}
.arrow:hover:before {
border-color:white;
}
</style>
<script>
document.querySelectorAll('.left').forEach(elm => {
elm.onclick = function () {
let div = this.parentNode.querySelector('.hscroll_wrapper div');
div.scrollLeft -= (div.clientWidth / 2);
};
});
document.querySelectorAll('.right').forEach(elm => {
elm.onclick = function () {
let div = this.parentNode.querySelector('.hscroll_wrapper div');
div.scrollLeft += (div.clientWidth / 2);
};
});
</script>
まとめ
PCとスマホの両対応が求められる昨今、レスポンシブデザインで横長のレイアウトを縦長にするのは良いけれど、それであまりに縦長になりすぎるのもちょっとどうなのかなぁ、と思い、先日は「CSSだけで数珠つなぎに無限ループするニュースティッカーを作ってみる」と題した記事を投稿しました。
これはこれで使いみちがあるのですが、複数配置しなければならないケースにおいては目にやさしくない。複数行のニュースティッカーがずらずら横スクロールしていく姿は株価ボードでも眺めているようで疲れてきますw
そんなわけで今回はこのような手動で横スクロールできる画面を作ってみたのでした。
同じような悩みを抱えているどこかの誰かのお役に立てば幸いです。