ご質問・お見積り等お気軽にご相談ください
お問い合わせ

HTML, CSS, JavaScript/jQueryで「ボトムシート」を作ってみた

HTML, CSS, JavaScript/jQueryで「ボトムシート」を作ってみた

この度は、株式会社ウェブネーションをご訪問頂き、誠にありがとうございます。

今回はHTML, CSS, JavaScript/jQueryで、「ボトムシート」というUI(ユーザインターフェース)を作ってみましたので、実際に動かした時の動画とそのコードと共に紹介いたします。

最後までお読みいただけますと幸いです。

  • 2023年10月11日追記:見出しと目次、引用元を新しく追加しました。
  • 2023年10月11日追記:アイキャッチ画像を変更しました。
  • 2023年12月29日追記:一部のコードと動画を変更しました。

ボトムシートとは?

まず、「ボトムシート」とは何かをご説明いたします。

みなさんは、スマートフォンを使っていると下の画像のようなものを見かけることがありませんか?

そうです、これがズバリ「ボトムシート」です。

つまりボトムシートとは、最もアピールしたい情報とは別に、他にもアピールしたい情報を補足情報として、画面下部からコンテンツを表示するUI(ユーザインターフェース)のことです。[1]

特にスマートフォンアプリ/モバイルアプリなどを使っていると、よく見かけるものかもしれませんね。

そこで今回は、そのボトムシートが、Webでも使えるのではないかと思い、この度作ってみました。

実際の「ボトムシート」の動作

実際に動かしたときの動画と、ボトムシートを動かすためのコード3つ(HTML, CSS, JavaScript/jQuery)は、下の通りになります。

※2023年10月11日追記:完成図のレイアウトは、CodingNepal様が作成したものを参考にしました。[2]

実際に動かしたときの動画

パソコン画面
スマートフォン画面

HTML

<div class="open-bottom-sheet-button">
	<span>Click Here!</span>
</div>
<div class="bottom-sheet">
	<div><span class="sheet-drag-bar">-------</span><span class="close-button">✖️</span></div>
	<div class="content">
		<span>
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
			Hello World!! Hello World!! Hello World!! Hello World!! Hello World!!
		</span>
	</div>
</div>
<div class="mask"></div>

CSS

<style>
	body {
		background-color: #bef3de;
	}
	
	.open-bottom-sheet-button {
		cursor: pointer;
		background-color: #29eda2;
		color: #ffffff;
		padding: 10px;
		margin: auto;
		top: 0;
		right: 0;
		left: 0;
		bottom: 0;
		display: flex;
		text-align: center;
		position: absolute;
		height: fit-content;
		align-items: center;
		width: fit-content;
		user-select: none;
		border-radius: 10px;
	}
	.bottom-sheet {
		border-radius: 10px;
		background-color: #ffffff;
		padding: 10px 30px 30px 30px;
		margin: 0 50px 0 50px;
		height: 100vh;
		position: fixed;
		top: 100vh;
		overflow-y: hidden;
	}
	.sheet-drag-bar {
		margin: auto;
		text-align: center;
		display: block;
		padding-bottom: 10px;
		letter-spacing: -1px;
		user-select: none;
		cursor: pointer;
	}
	.close-button {
		position: absolute;
		top: 15px;
		right: 15px;
		cursor: pointer;
	}
	.content {
		max-height: 90vh;
		overflow-y: scroll;
	}
	.mask {
		background-color: #000000;
		opacity: 0.5;
		display: none;
		width: 100%;
		position: fixed;
		left: 0;
		right: 0;
		top: 0;
		bottom: 0;
		z-index: 2;
	}

	@media screen and (max-width:500px){
		.bottom-sheet {
			margin: 0;
			left: 0;
		}
	}
</style>

JavaScript / jQuery

<script>

	let isOpened = false;
	let isDragging = false;
	let startHeight;

	$(() => {
		// ボトムシートを開く
		$('.open-bottom-sheet-button').on('click', () => {
			if(isOpened) return false;
			moveBottomSheet(-50, 80);
			openAction();
		});

		// ボトムシートを閉じる(ボトムシート外もしくは閉じるボタンをクリックで発動)
		$(document).on('click touchstart', function(e){
			if($(e.target).closest('.mask').length > 0 || $(e.target).closest('.close-button').length > 0){
				closeAction();
				moveBottomSheet(100, 100);
			}
		});

		// ボトムシートの操作
		$(document)
		.on('mousedown touchstart', '.sheet-drag-bar', (e) => dragStart(e))
		.on('mousemove touchmove', document, (e) => dragging(e))
		.on('mouseup mouseleave', document, (e) => dragStop(e))
		.on('touchend', '.bottom-sheet > div:not(.content)', (e) => dragStop(e))
		.on('touchleave', window, (e) => dragStop(e));

		// ドラッグ操作開始
		function dragStart(e){
			isDragging = true;
			startHeight = parseInt($('.bottom-sheet').css('top')) - $('.bottom-sheet').offset().top;
		}

		// ドラッグ操作中
		function dragging(e){
			if(!isDragging) return false;
			let deltaY = (e.clientY<0 ? 0 : e.clientY) ?? (e.changedTouches[0].clientY<0 ? 0 : e.changedTouches[0].clientY)
			let newHeight = deltaY + parseInt($('.bottom-sheet').height())/2;
			$('.bottom-sheet').css({'top':newHeight+'px'});
		}

		// ドラッグ操作終了
		function dragStop(e){
			if(!isOpened || !isDragging) return false;
			let deltaY = e.clientY ?? e.changedTouches[0].clientY;
			// ボトムシート全部展開
			if(deltaY < window.innerHeight*0.3){
				moveBottomSheet(-50, 50);
			// ボトムシート半分展開
			}else if(deltaY >= window.innerHeight*0.3 && deltaY <= window.innerHeight*0.7){
				moveBottomSheet(-50, 80);
			// ボトムシートを閉じる(ボトムシート最下部付近でマウスを離した時に発動)
			}else{
				closeAction(); moveBottomSheet(100, 100);
			}
			isDragging = false;
		}

		// ボトムシートを特定の位置に移動
		function moveBottomSheet(distance, position){
			$('.bottom-sheet').css({
				'transform':'translateY('+String(distance)+'vh)',
				'top':String(position)+'vh',
				'transition':'0.1s ease-out',
			});
		}

		// ボトムシートを開いた時の処理
		function openAction(){
			isOpened = true;
			$('body').css({'overflow':'hidden'});
			$('.mask').css({'display':'block'});
		}

		// ボトムシートをたたんだ時の処理
		function closeAction(){
			isOpened = false;
			$('body').css({'overflow':'auto'});
			$('.mask').css({'display':'none'});
		}
	});

</script>

最後に

いかがでしたでしょうか?

ボトムシートは、スマートフォンアプリ/モバイルアプリ開発、もしくはスマートフォンの使用を想定したWeb開発にあたっては、柔軟に活用できるUIかもしれませんね。

今回はここまでとさせていただきます。

最後までお読みいただき、誠にありがとうございます。

参考サイト

  1. Bottom sheets – Material Design 3(2023年9月30日閲覧)
  2. Create Draggable Bottom Sheet Modal in HTML CSS & JavaScript(2023年9月30日閲覧)
  3. iPhoneを持っている人がオンになっています(2023年10月11日閲覧、アイキャッチ画像)

この記事を書いた人
通りすがりのエンジニアB
通りすがりのエンジニアB(匿名)です。