本記事では、フォーム入力の進捗度を可視化するプログレスバーの実装方法を紹介します。
プログレスバーを設置するだけで、入力の現在地がひと目で分かり、離脱抑止と完了率向上に寄与します。軽量で既存フォームに簡単に組み込み可能です。
ポイント: 視覚的な進捗提示は入力ストレスの低減に有効
導入効果
- 完了率の見える化でモチベーション維持
- 入力体験の向上(スムーズなアニメーション)
- ページ上部に固定され常に視認可能
- 軽量・依存なしで既存フォームにすぐ追加
- 完了率の見える化でモチベーション維持
- 入力体験の向上(スムーズなアニメーション)
- ページ上部に固定され常に視認可能
- 軽量・依存なしで既存フォームにすぐ追加
できること(メリット詳細)
デモサイトで動作をご確認頂けます。
テキストを入力してみたり、ラジオボタン、チェックボックス等を選択したりして動作をご確認ください。
- 離脱抑止: 目標達成までの距離が明確になり完了率向上が期待
- UX向上: スムーズな変化と演出で入力体験がリッチに
- 常時可視: 上部固定により入力中も進捗を見失わない
- 導入容易: 既存フォームにHTML/CSS/JSを追加するだけ
- ブランド適合: 色や動きを簡単にブランドトーンへ調整可能
コードの説明と実装方法
プログレスバーのCSS
/* CSS ここから */
:root {
/* 背景色(白) */
--background: #FFFFFF;
/* 枠線色(薄いグレー) */
--border: #B3B3B3;
/* プログレスバーのグラデーション色(明るい側/濃い側) */
--gradient-light: #00a1eb;
--gradient-dark: #00ffd9;
}
.progress-bar-area {
padding: 10px 18px 6px 18px;
background-color: var(--background);
border-bottom: 2px solid var(--border);
}
.progress-bar-container {
background-color: var(--background);
overflow: hidden;
margin: 8px auto;
border: 1px solid var(--border);
border-radius: 10px;
}
.progress-bar {
height: 10px;
width: 0%;
/* グラデーションの色は :root の --gradient-light / --gradient-dark を変更 */
background: linear-gradient(
to right,
var(--gradient-light) 0%,
var(--gradient-dark) 25%,
var(--gradient-light) 50%,
var(--gradient-dark) 75%,
var(--gradient-light) 100%
);
background-size: 200% 100%;
background-position: 0% 0%;
/* 下記のアニメーション速度は JS 側でも上書きできます */
animation: gradient-move 8s linear infinite;
transition: width 1s ease; /* 伸縮速度は JS 側でも上書きできます */
position: relative;
overflow: visible;
border-radius: 10px;
}
@keyframes gradient-move {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 0%;
}
}
.fixed-progress {
position: fixed;
top: 0;
z-index: 1000;
width: calc(100% - 36px);
}
/* CSS ここまで */
実装方法
CSSはページ内の
PC版とスマートフォン版でCSSを分ける必要はありません。
ただし、スマートフォン版のCSSを追加する場合は、PC版のCSSよりも下に記載してください。
<style>タグ内に記載してください。
PC版とスマートフォン版でCSSを分ける必要はありません。
ただし、スマートフォン版のCSSを追加する場合は、PC版のCSSよりも下に記載してください。
プログレスバーのHTML
<div class="progress-bar-area"> <span>進捗度: </span> <span id="progress-text"></span> <div class="progress-bar-container"> <div class="progress-bar" id="progress-bar"></div> </div> </div>
実装方法
このHTMLを使用して進捗度バーを表示します。
<body class="body">タグと
<center>タグの間に記載してください
プログレスバーを制御するJavascript
<script>
(function () {
'use strict';
const SETTINGS = {
// バーの幅が変わるトランジション時間(ミリ秒)
TRANSITION_MS: 1000,
// 背景グラデーション(CSS keyframes: gradient-move)の再生時間(ミリ秒)
GRADIENT_ANIM_MS: 8000,
// 伸縮方向に応じて付与するリップル演出の長さ(ミリ秒)
RIPPLE_MS: 700,
// スクロールがこの値以上で固定クラスを付与
FIXED_SCROLL_THRESHOLD: 1,
// 固定表示する際の上部オフセット(px)。必要に応じて変更
FIXED_TOP_PX: 0,
};
// =============================================================
// 定数
// =============================================================
const INPUT_SELECTOR = 'input, textarea, select';
const PROGRESS_BAR_ID = 'progress-bar';
const PROGRESS_TEXT_ID = 'progress-text';
const PROGRESS_CONTAINER_SELECTOR = '.progress-bar-area';
// -------------------------------------------------------------
// ユーティリティ
// -------------------------------------------------------------
function getGroupName(name) {
return name.split(':')[0];
}
function isFieldFilled(element) {
if (element.type === 'text' || element.type === 'password' || element.tagName === 'TEXTAREA') {
return element.value.trim() !== '';
}
if (element.tagName === 'SELECT') {
return element.value !== '';
}
return false;
}
function collectVisibleFormElements() {
return Array.from(document.querySelectorAll(INPUT_SELECTOR)).filter(el =>
el.offsetParent !== null &&
el.type !== 'hidden' &&
el.type !== 'submit' &&
el.type !== 'button' &&
el.type !== 'reset'
);
}
function groupFieldsByName(elements) {
const groups = {};
elements.forEach(el => {
const name = el.getAttribute('name');
if (!name) return;
if (el.type === 'hidden') return;
const group = getGroupName(name);
if (!groups[group]) groups[group] = [];
groups[group].push(el);
});
return groups;
}
function computeProgress(groups) {
let completedGroups = 0;
let totalGroups = 0;
Object.values(groups).forEach(groupFields => {
const visibleFields = groupFields.filter(el => el.type !== 'hidden');
if (visibleFields.length === 0) return;
totalGroups++;
const isRadioOrCheckbox = visibleFields.some(el => el.type === 'radio' || el.type === 'checkbox');
if (isRadioOrCheckbox) {
if (visibleFields.some(el => el.checked)) {
completedGroups++;
}
} else {
if (visibleFields.every(isFieldFilled)) {
completedGroups++;
}
}
});
// グループが0件のときは 0% を返す(初期表示でNaN%にならないように)
const percent = totalGroups > 0 ? (completedGroups / totalGroups) * 100 : 0;
return { completedGroups, totalGroups, percent };
}
function renderProgress(progress) {
const progressBar = document.getElementById(PROGRESS_BAR_ID);
const progressText = document.getElementById(PROGRESS_TEXT_ID);
if (!progressBar || !progressText) return;
progressBar.style.width = progress.percent + '%';
progressText.innerText = Math.round(progress.percent) + '%';
console.log(progress.completedGroups);
console.log(progress.totalGroups);
}
// -------------------------------------------------------------
// 付随機能(固定表示/リップルアニメーション)
// -------------------------------------------------------------
function setupFixedProgressOnScroll() {
window.addEventListener('scroll', function () {
const progressBarContainer = document.querySelector(PROGRESS_CONTAINER_SELECTOR);
if (!progressBarContainer) return;
const scrollTop = window.scrollY;
const shouldFix = scrollTop >= SETTINGS.FIXED_SCROLL_THRESHOLD;
if (shouldFix) {
progressBarContainer.classList.add('fixed-progress');
// 固定時の上端位置を設定(CSSのtopを上書き)
progressBarContainer.style.top = SETTINGS.FIXED_TOP_PX + 'px';
} else {
progressBarContainer.classList.remove('fixed-progress');
progressBarContainer.style.top = '';
}
});
}
function setupRippleAnimation() {
const progressBar = document.getElementById(PROGRESS_BAR_ID);
if (!progressBar) return;
// 伸縮速度(transition)を設定
progressBar.style.transition = 'width ' + SETTINGS.TRANSITION_MS + 'ms ease';
// グラデーションの移動速度(CSS変数で渡せないため style.animationDuration を直接設定)
// 既存CSS: animation: gradient-move 8s linear infinite;
progressBar.style.animationDuration = SETTINGS.GRADIENT_ANIM_MS + 'ms';
let rippleTimeout;
let previousWidth = parseFloat(progressBar.style.width) || 0;
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
const newWidth = parseFloat(progressBar.style.width) || 0;
if (newWidth === previousWidth) return;
const rippleClass = newWidth > previousWidth ? 'rippling-right' : 'rippling-left';
progressBar.classList.remove('rippling-right', 'rippling-left');
void progressBar.offsetWidth; // Reflow
progressBar.classList.add(rippleClass);
clearTimeout(rippleTimeout);
rippleTimeout = setTimeout(() => {
progressBar.classList.remove(rippleClass);
}, SETTINGS.RIPPLE_MS);
previousWidth = newWidth;
}
});
});
observer.observe(progressBar, { attributes: true });
}
// -------------------------------------------------------------
// 初期化
// -------------------------------------------------------------
function updateProgressBar() {
const elements = collectVisibleFormElements();
const groups = groupFieldsByName(elements);
const progress = computeProgress(groups);
renderProgress(progress);
}
function init() {
document.querySelectorAll(INPUT_SELECTOR).forEach(element => {
element.addEventListener('input', updateProgressBar);
});
setupFixedProgressOnScroll();
setupRippleAnimation();
updateProgressBar();
}
window.addEventListener('load', init);
})();
</script>
実装方法
<body class="body">タグ内の一番下に設置してください。
カスタマイズ(色・動きの変更ガイド)
ここでは、CSSと JavaScript の「先頭の変数」だけを変えることで、 見た目の色やアニメーションの速さ、固定位置を簡単に調整する方法を説明します。
ポイント
「先頭の変数だけ」を変える設計なので、プログラムの知識がなくても安心して調整できます。
「先頭の変数だけ」を変える設計なので、プログラムの知識がなくても安心して調整できます。
1. 色の変更
次の4つの項目を変更すると、見た目の色が一括で切り替わります。
:root {
--background: #FFFFFF; /* 背景の色(白) */
--border: #B3B3B3; /* 枠線の色(薄いグレー) */
--gradient-light: #00a1eb;
--gradient-dark: #00ffd9;
}
- バーの色を変えたい:
--gradient-lightと--gradient-darkをお好みの2色に。 - 単色にしたい: バーのCSS内の
background: linear-gradient(...)をbackground-color: 好きな色;に変更。 - 枠線の色:
--borderを変更。 - 背景の色:
--backgroundを変更。
色の書き方は #RRGGBB(例: #16A34A)や、色名(例: red)でもOKです。
2. 動き(速さ・固定位置)の変更
次の項目を変えるだけで、アニメーションの速さや固定位置を調整できます。
const SETTINGS = {
TRANSITION_MS: 1000, // バーが伸び縮みする速さ(ミリ秒)
GRADIENT_ANIM_MS: 8000,// 背景の流れる速さ(ミリ秒)
RIPPLE_MS: 700, // 伸縮時の演出の長さ(ミリ秒)
FIXED_SCROLL_THRESHOLD: 1, // 何pxスクロールで上部固定にするか
FIXED_TOP_PX: 0, // 固定するときの上からの位置(px)
};
- 伸縮の速さ:
TRANSITION_MS(数値を小さくすると速くなります) - 背景の流れる速さ:
GRADIENT_ANIM_MS(小さいほど速い) - 演出の長さ:
RIPPLE_MS(演出が長く続きます) - 固定の開始位置:
FIXED_SCROLL_THRESHOLD(この値以上スクロールすると固定) - 固定時の位置:
FIXED_TOP_PX(ヘッダーがある場合はその高さぶん加算)
3. 作業の流れ(かんたん手順)
- 色を変える:
code3-1のいちばん上(:root {...})の数値を変更 - 動きを変える:
code2-1のいちばん上(SETTINGS {...})の数値を変更 - SPIRALへ反映し、ブラウザで表示を確認
4. うまく変わらない時
- ブラウザをリロードしてください。
【お知らせ】本実装の構築代行・カスタマイズを承ります
記事でご紹介した内容や、貴社の要件に合わせた柔軟なカスタマイズ開発を承っております。
SPIRALの専門チームが要件定義から実装まで一貫してサポートいたしますので、まずはお見積もりをご依頼ください。