読み込み中...

JavaScript初心者必見!簡単ライブラリの作成・活用方法15選

JavaScript初心者向けライブラリ作成と活用方法の解説 JS
この記事は約36分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptライブラリは、よく使う処理を再利用できる形にまとめ、DOM操作、イベント処理、通信、UI部品などを同じ書き方で扱うためのコード群です。そのため、作成方法を理解しておくと、小さな関数の集合から画面部品まで段階的に整理できます。

初心者がつまずきやすいのは、単なる関数の寄せ集めと、呼び出し側から扱いやすいライブラリ設計の違いです。この違いは、constfunctionclassreturnexportの役割を分けて考えると理解しやすくなります。

そのため、最初の目標は大きな仕組みを作ることではなく、同じ処理を何度も書かずに済む単位へ分けることになるのが基本です。関連する基礎として、繰り返し処理はJavaScriptのforEachでreturnを活用する6つのテクニック、配列変換はforEach・mapの使い分け術も合わせて読むと整理しやすいです。

動作確認環境
  • ECMAScript 2024 相当の構文
  • Google Chrome 126 / Node.js 20 LTS
  • ブラウザ API は MDN Web Docs の仕様説明を参照
📖 この記事で学べること
  • JavaScriptライブラリの基本構造と作成方法
  • オブジェクト、モジュール、名前空間の使い分け
  • DOM操作、イベント、通信をまとめる設計
  • オプション追加やプラグイン化によるカスタマイズ
  • UI部品として応用する際の注意点

JavaScriptライブラリとは

JavaScriptライブラリとは、特定の目的で再利用される関数、オブジェクト、クラス、設定値をまとめたものです。その中心には、呼び出し側が迷わず使えるAPIを用意し、内部の細かな処理を隠す考え方があります。

一般に、ライブラリはアプリケーション全体を支配するものではなく、必要な場面で呼び出される部品として設計されます。一方、フレームワークは画面遷移や状態管理の流れまで含めて全体構造を決める場合が多く、役割に違いがあるのが目安です。

公式情報を確認する場合は、標準 API の動作をMDN の JavaScript リファレンスで調べるのが堅実です。DOM API についてはDocument.querySelector()のように、個別ページで返り値や例外の扱いまで確認できます。

分類主な用途使う構文注意点向く場面
オブジェクト関数の整理{}状態が共有されやすい小規模な部品
関数処理の分割function命名衝突に注意単一処理
アロー関数短い処理=>thisの束縛が異なるコールバック
クラス状態を持つ部品class初期化順を揃えるUI部品
コンストラクタ初期値の注入constructor引数設計が肥大化しやすい複数インスタンス
メソッド操作の公開method()責務を広げすぎない外部呼び出し
プロパティ値の保持property直接変更を許すか決める設定値
モジュール依存関係の整理importビルド環境を確認する分割開発
エクスポート外部公開export公開範囲を絞る再利用
即時関数スコープ分離IIFE古い書き方になりやすい単一ファイル
名前空間衝突回避myLibraryグローバル化に注意既存ページ追加
DOM取得要素操作querySelectornull確認が必要画面更新
テキスト変更表示更新textContentHTMLを解釈しない安全な表示
HTML挿入動的描画innerHTML入力値を直接入れないテンプレート
クラス操作見た目の切替classListCSSとの対応を保つタブやモーダル
イベント操作検知addEventListener解除方法も考えるクリック処理
通信データ取得fetch失敗時の分岐が必要一覧追加
旧式通信互換用途XMLHttpRequest新規ならfetch優先古い環境
非同期待機処理async例外処理を入れるAPI連携
待機Promise解決await関数にasyncが必要読みやすい通信
配列反復複数要素処理forEach途中終了に向かない一覧処理
条件分岐例外回避if分岐を増やしすぎない入力チェック
正規表現形式判定RegExp厳密な検証には限界がある簡易判定
タイマー定期実行setInterval停止処理が必要自動再生
停止タイマー解除clearIntervalIDを保持する省リソース
属性取得HTML値の参照getAttribute存在確認が必要モーダル連携
データ属性値の埋め込みdata-*文字列として扱われる価格やID
スタイル見た目変更styleCSSクラス優先も検討する簡易変更
オプション挙動の変更Object.assign深いコピーではない初期設定
プラグイン拡張機能registerPlugin実行権限を絞る後付け拡張

ライブラリの作成方法

作成方法の出発点は、外部へ見せる名前と内部で使う処理を分けることです。その分離ができると、関数名を変えずに内部処理を改善しやすくなり、利用側のコードも壊れにくくなります。

基本的な構造

基本形では、myLibraryというオブジェクトに値と処理をまとめます。この形は短く書けるため、学習用の作成方法として扱いやすく、プロパティとメソッドの関係も見えやすいです。

const myLibrary = { property: 'value', method: function () { return this.property; } };

結果: 期待される出力は、myLibrary.method()を呼び出したときに'value'が返る状態です。

モジュールパターン

その次に扱いやすい作成方法が、即時関数で内部スコープを作るモジュールパターンです。公開する値だけをreturnで返し、privateVarprivateFuncを外部から直接触れない形にします。

const myLibrary = (function () { const privateVar = 'private'; function privateFunc() { return privateVar; } return { publicVar: 'public', publicFunc: function () { return privateFunc(); } }; })();

結果: 期待される出力は、myLibrary.publicFunc()'private'が返り、privateVarへ直接アクセスできない状態です。

名前空間パターン

名前空間パターンでは、関連機能をひとつの大きなオブジェクトへまとめます。そのため、module1module2のように領域を分け、同じ名前の関数がページ内で衝突するリスクを下げられます。

const myLibrary = { module1: { method1: function () { return 'module1'; } }, module2: { method2: function () { return 'module2'; } } };

結果: 期待される出力は、myLibrary.module1.method1()'module1'myLibrary.module2.method2()'module2'が返る構成です。

💡 Tips: 小さなライブラリではオブジェクト形式、外部へ出す値を絞りたい場合はモジュール形式、機能数が増える場合は名前空間形式に寄せると整理しやすくなります。

ライブラリの使い方

ライブラリの使い方は、定義側と呼び出し側を分けて読むと分かりやすくなります。定義側ではgetElementsetTextのような操作名を用意し、呼び出し側では目的に合わせて値を渡するのがポイントです。

DOM操作やイベント処理の基礎が曖昧な場合は、関連するJavaScriptイベント徹底解説イベントハンドラの実践サンプルを先に押さえると、コードの流れを追いやすいです。

サンプルコード1:簡単なDOM操作

この例では、document.querySelectorで要素を取り、textContentで文字列を書き換えます。そのため、処理の利用側はセレクタと表示文字列だけを意識すればよくなります。

const myLibrary = { getElement: function (selector) { return document.querySelector(selector); }, setText: function (element, text) { if (element) { element.textContent = text; } } }; const element = myLibrary.getElement('#target'); myLibrary.setText(element, '変更後のテキスト');

結果: 期待される表示は、id='target'の要素が存在する場合に文字列が変更後のテキストへ変わる状態です。

サンプルコード2:イベントリスナーの追加

イベント処理は、対象要素、イベント種別、実行する関数を分けると再利用しやすくなります。この形にしておくと、click以外のinputchangeにも同じ考え方を使えます。

const myLibrary = { addEventListener: function (element, type, callback) { if (element) { element.addEventListener(type, callback); } } }; const button = document.querySelector('#button'); myLibrary.addEventListener(button, 'click', function () { alert('ボタンがクリックされました'); });

結果: 期待される表示は、#buttonをクリックしたときにアラートでボタンがクリックされましたと示される状態です。

サンプルコード3:Ajax通信の実装

通信処理は、古い環境との互換を考える場合にXMLHttpRequestが使われることがあります。一方、新しく作る処理ではfetchのほうが読みやすい場面も多く、どちらも失敗時の扱いを分岐させます。

const myLibrary = { ajax: function (url, callback) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { callback(xhr.responseText); } }; xhr.open('GET', url, true); xhr.send(); } }; const url = 'https://api.example.com/data'; myLibrary.ajax(url, function (response) { console.log('取得したデータ: ', response); });

結果: 期待される出力は、通信先が有効でstatus200の場合に、コンソールへ取得データが渡される状態です。

ライブラリの対処法と注意点

ライブラリを使いやすくするには、正常系だけでなく、要素が見つからない場合、通信に失敗した場合、想定外の値が渡された場合も考えます。そのため、null確認、trycatchconsole.errorを必要な場所に置く設計が現実的です。

ブラウザの互換性

ブラウザ互換性では、対象ユーザーの環境と利用する API を照らし合わせます。たとえばclassListfetchdatasetURLSearchParamsなどは広く使われますが、古い環境まで含めるなら確認が必要になるのが一般的です。

ただし、すべての環境に合わせてコードを増やすと、作成方法そのものが複雑になります。その場合は、サポート対象を明記し、必要に応じてpolyfillやビルドツールを使う判断になります。

パフォーマンス

パフォーマンス面では、スクロールやリサイズのように高頻度で発火するイベントへ注意するのが現実的です。このような処理では、debouncethrottleで呼び出し回数を制御し、DOMの読み書きをまとめると負荷を抑えやすくなります。

一方、最初から過度に最適化すると、コードの読みやすさが下がる場合があります。そのため、処理の責務を分け、測定可能な問題が見えた段階で改善する流れが扱いやすいです。

⚠️ 注意: 動作しているように見えるコードでも、innerHTMLへ外部入力を直接入れる設計は避けますし、ここがポイントです。表示文字列だけならtextContentを使い、HTMLを組み立てる場合は入力値の扱いを分けてください。

ライブラリのカスタマイズ方法

カスタマイズでは、利用者が変えたい値と、内部で固定したい処理を分けます。そのため、初期値をdefaultOptionsに置き、呼び出し側のoptionsと合わせる作成方法がよく使われます。

サンプルコード4:オプションの追加

この例では、文字色と文字サイズを外部から変えられるようにすると整理できます。Object.assignで初期値と指定値を合わせるため、未設定の項目は既定値のまま残ります。

const myLibrary = function (options) { const defaultOptions = { color: 'red', fontSize: '16px' }; const mergedOptions = Object.assign({}, defaultOptions, options); this.changeStyle = function (element) { if (!element) return; element.style.color = mergedOptions.color; element.style.fontSize = mergedOptions.fontSize; }; }; const customOptions = { color: 'blue', fontSize: '20px' }; const libInstance = new myLibrary(customOptions); const targetElement = document.querySelector('#target'); libInstance.changeStyle(targetElement);

結果: 期待される表示は、#targetの文字色がblue、文字サイズが20pxへ変わる状態です。

サンプルコード5:プラグインの実装

プラグイン方式では、後から処理を登録し、名前で呼び出します。この形は拡張しやすい反面、登録できる関数の範囲を広げすぎると、どの処理が実行されるか追いにくくなります。

const myLibrary = function () { this.plugins = {}; }; myLibrary.prototype.registerPlugin = function (pluginName, pluginFunc) { this.plugins[pluginName] = pluginFunc; }; myLibrary.prototype.runPlugin = function (pluginName) { if (this.plugins[pluginName]) { this.plugins[pluginName](); } else { console.error('プラグインが存在しません。'); } }; const libInstance = new myLibrary(); libInstance.registerPlugin('samplePlugin', function () { console.log('プラグインが実行されました。'); }); libInstance.runPlugin('samplePlugin');

結果: 期待される出力は、登録済みのsamplePluginを呼び出したときにコンソールへプラグインが実行されました。と出る状態です。

ℹ️ 補足: Object.assignは浅いコピーです。入れ子の設定を扱う場合は、設定構造を浅く保つか、深いマージのルールを別途決めると混乱を避けやすくなります。

ライブラリの応用例

応用例では、DOM操作、イベント、状態管理、通信を組み合わせます。そのため、単体の関数よりも、constructorで初期化し、initでイベントを結び、公開メソッドで状態を変える設計が読みやすくなると理解できます。

これらの例は、実際のサービスへそのまま投入する完成品ではなく、作成方法を理解するための参考コードです。拡張子やファイル分割の扱いはJavaScriptで拡張子を活用する方法12選も参照すると整理できます。

サンプルコード6:スライドショー

スライドショーでは、現在位置をcurrentSlideに持ち、次の表示へ移るたびにactiveクラスを付け替えます。その結果、CSS側は.activeの表示だけを担当できると覚えるとよいでしょう。

class SlideShow { constructor(container, options) { this.container = container; this.slides = this.container.querySelectorAll('.slide'); this.currentSlide = 0; this.options = options || {}; this.interval = this.options.interval || 3000; this.autoPlay = this.options.autoPlay || false; this.timer = null; if (this.autoPlay) { this.startAutoPlay(); } } nextSlide() { this.slides[this.currentSlide].classList.remove('active'); this.currentSlide = (this.currentSlide + 1) % this.slides.length; this.slides[this.currentSlide].classList.add('active'); } startAutoPlay() { this.timer = setInterval(() => { this.nextSlide(); }, this.interval); } stopAutoPlay() { clearInterval(this.timer); this.timer = null; } } const slideShowContainer = document.querySelector('.slideshow-container'); const slideShow = new SlideShow(slideShowContainer, { interval: 2000, autoPlay: true });

結果: 期待される表示は、.slide要素のactiveクラスが一定間隔で切り替わる状態です。

サンプルコード7:モーダルウィンドウ

モーダルは、開くボタンと対象要素をdata-modalで結び付けると扱いやすくなります。このとき、閉じるボタンの存在確認を入れると、HTML構造の変更にも対応しやすいです。

class Modal { constructor(trigger, options) { this.trigger = trigger; this.modalId = this.trigger.getAttribute('data-modal'); this.modal = document.getElementById(this.modalId); this.closeButton = this.modal.querySelector('.close'); this.options = options || {}; this.init(); } init() { this.trigger.addEventListener('click', () => { this.open(); }); this.closeButton.addEventListener('click', () => { this.close(); }); } open() { this.modal.classList.add('active'); } close() { this.modal.classList.remove('active'); } } const modalTriggers = document.querySelectorAll('.modal-trigger'); modalTriggers.forEach((trigger) => { new Modal(trigger); });

結果: 期待される表示は、.modal-triggerをクリックすると対象モーダルへactiveが付き、閉じる操作で外れる状態です。

サンプルコード8:タブメニュー

タブメニューは、押されたタブの番号と表示する内容の番号を合わせます。そのため、tabscontentsの並び順がずれると表示もずれるため、HTML側の構造を揃える必要があります。

class TabMenu { constructor(tabMenu, options) { this.tabMenu = tabMenu; this.tabs = tabMenu.querySelectorAll('.tab'); this.contents = tabMenu.querySelectorAll('.content'); this.options = options || {}; this.init(); } init() { this.tabs.forEach((tab, index) => { tab.addEventListener('click', () => { this.switchTab(index); }); }); } switchTab(index) { this.tabs.forEach((tab) => { tab.classList.remove('active'); }); this.contents.forEach((content) => { content.classList.remove('active'); }); this.tabs[index].classList.add('active'); this.contents[index].classList.add('active'); } } const tabMenus = document.querySelectorAll('.tab-menu'); tabMenus.forEach((tabMenu) => { new TabMenu(tabMenu); });

結果: 期待される表示は、選択した.tabと対応する.contentだけにactiveが付く状態です。

サンプルコード9:アコーディオンメニュー

アコーディオンは、見出しを押すと本文の開閉状態が変わる UI です。この例ではclassList.toggleを使い、同じ操作で開閉を切り替えます。

class AccordionMenu { constructor(menu, options) { this.menu = menu; this.items = menu.querySelectorAll('.item'); this.options = options || {}; this.init(); } init() { this.items.forEach((item) => { const header = item.querySelector('.header'); header.addEventListener('click', () => { this.toggleItem(item); }); }); } toggleItem(item) { const content = item.querySelector('.content'); content.classList.toggle('active'); } } const accordionMenus = document.querySelectorAll('.accordion-menu'); accordionMenus.forEach((menu) => { new AccordionMenu(menu); });

結果: 期待される表示は、各.headerをクリックしたときに対応する.contentactiveが切り替わる状態です。

サンプルコード10:ドラッグ&ドロップ

ドラッグ&ドロップでは、開始時に対象IDをdataTransferへ入れ、ドロップ時に取り出します。そのため、dragoverpreventDefaultを呼び、ドロップ可能な状態にする必要があります。

function onDragStart(event) { event.dataTransfer.setData('text/plain', event.target.id); } function onDragOver(event) { event.preventDefault(); } function onDrop(event) { event.preventDefault(); const draggableElementId = event.dataTransfer.getData('text/plain'); const draggableElement = document.getElementById(draggableElementId); event.target.appendChild(draggableElement); } const draggableItems = document.querySelectorAll('.draggable'); const dropZones = document.querySelectorAll('.drop-zone'); draggableItems.forEach((item) => { item.addEventListener('dragstart', onDragStart); }); dropZones.forEach((zone) => { zone.addEventListener('dragover', onDragOver); zone.addEventListener('drop', onDrop); });

結果: 期待される表示は、.draggable要素を.drop-zoneへ移動できる状態です。

サンプルコード11:インフィニットスクロール

インフィニットスクロールは、ページ下部へ近づいたタイミングで新しいデータを読み込みます。一方、連続発火による多重通信が起こりやすいため、実運用では読み込み中フラグや失敗時の分岐を足します。

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'><title>インフィニットスクロール</title><style>.item { width: 100%; padding: 20px; border: 1px solid #ccc; margin-bottom: 10px; }</style></head><body><div id='container'></div><script>let page = 1; async function fetchAndDisplayItems() { const response = await fetch(`/api/items?page=${page}`); const items = await response.json(); const container = document.getElementById('container'); items.forEach(item => { const newItem = document.createElement('div'); newItem.className = 'item'; newItem.textContent = item.content; container.appendChild(newItem); }); page++; } function onScroll() { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight; const clientHeight = document.documentElement.clientHeight; if (scrollTop + clientHeight >= scrollHeight - 50) { fetchAndDisplayItems(); } } fetchAndDisplayItems(); window.addEventListener('scroll', onScroll);</script></body></html>

結果: 期待される表示は、ページ下部へ近づくたびに/api/itemsから取得した項目が#containerへ追加される状態です。

サンプルコード12:パララックススクロール

パララックスは、背景要素を前景より遅く動かして奥行きを見せる表現です。この処理ではtransformを変えるため、topmarginを頻繁に変えるより描画負荷を抑えやすい場合があります。

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'><title>パララックススクロール</title><style>body { margin: 0; font-family: Arial, sans-serif; } .parallax-section { position: relative; height: 100vh; overflow: hidden; } .parallax-image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-size: cover; background-repeat: no-repeat; background-position: center; } .content { position: relative; text-align: center; padding-top: 30%; color: white; }</style></head><body><div class='parallax-section'><div class='parallax-image' id='image1' style='background-image: url(image1.jpg);'></div><div class='content'><h1>パララックススクロール</h1><p>立体的な効果を生み出すテクニックです。</p></div></div><script>function parallaxScroll() { const image1 = document.getElementById('image1'); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; image1.style.transform = `translateY(${scrollTop * 0.5}px)`; } window.addEventListener('scroll', parallaxScroll);</script></body></html>

結果: 期待される表示は、スクロール量に応じて#image1translateYが変わり、背景が遅れて動く状態です。

サンプルコード13:ショッピングカート

ショッピングカートは、商品名、価格、合計金額を同時に扱います。そのため、data-priceで価格を持たせ、追加と削除の関数で合計値を更新する流れにします。

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'><title>ショッピングカート</title><style>body { font-family: Arial, sans-serif; } .products, .cart { display: flex; flex-direction: column; } .product, .cart-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } button { margin-left: 10px; }</style></head><body><h1>ショッピングカート</h1><div class='products'><h2>商品一覧</h2><div class='product' data-price='1000'><span>商品A</span><button onclick='addToCart(this)'>カートに追加</button></div><div class='product' data-price='2000'><span>商品B</span><button onclick='addToCart(this)'>カートに追加</button></div><div class='product' data-price='3000'><span>商品C</span><button onclick='addToCart(this)'>カートに追加</button></div></div><div class='cart'><h2>カート内商品</h2><div class='cart-items'></div><div class='total'>合計金額: <span id='total-price'>0</span>円</div></div><script>const cartItems = document.querySelector('.cart-items'); const totalPrice = document.getElementById('total-price'); function addToCart(button) { const product = button.parentElement; const productName = product.querySelector('span').textContent; const productPrice = parseInt(product.getAttribute('data-price'), 10); const cartItem = document.createElement('div'); cartItem.className = 'cart-item'; cartItem.innerHTML = `<span>${productName} - ${productPrice}円</span><button onclick='removeFromCart(this)'>カートから削除</button>`; cartItems.appendChild(cartItem); const currentTotal = parseInt(totalPrice.textContent, 10); totalPrice.textContent = currentTotal + productPrice; } function removeFromCart(button) { const cartItem = button.parentElement; const itemPriceText = cartItem.querySelector('span').textContent.split(' - ')[1]; const itemPrice = parseInt(itemPriceText.slice(0, -1), 10); cartItems.removeChild(cartItem); const currentTotal = parseInt(totalPrice.textContent, 10); totalPrice.textContent = currentTotal - itemPrice; }</script></body></html>

結果: 期待される表示は、商品追加でカート内の商品行と合計金額が増え、削除で該当行と金額が減る状態です。

サンプルコード14:カレンダー

カレンダーは、HTML、CSS、スクリプトを分けると作成方法が追いやすくなります。HTMLは配置場所を用意し、CSSは見た目を整え、処理側で現在年月と曜日を組み立てます。

HTML

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'><title>サンプルコード14:カレンダー</title><link rel='stylesheet' href='styles.css'></head><body><div id='calendar'></div><script src='script.js'></script></body></html>

結果: 期待される表示は、#calendarを配置し、styles.cssscript.jsを読み込めるHTML構造です。

CSS(styles.css)

#calendar { display: inline-block; text-align: center; } #calendar .days-header { display: flex; justify-content: space-between; } #calendar .date-cell { width: 14.28%; padding: 5px; box-sizing: border-box; } #calendar .today { background-color: #f3f3f3; }

結果: 期待される表示は、曜日と日付セルが横幅に合わせて並び、当日セルへ薄い背景色が付く状態です。

JavaScript(script.js)

function displayCalendar() { const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); const today = now.getDate(); const calendar = document.getElementById('calendar'); const daysHeader = document.createElement('div'); daysHeader.className = 'days-header'; const days = ['日', '月', '火', '水', '木', '金', '土']; for (let i = 0; i < days.length; i++) { const dayHeader = document.createElement('div'); dayHeader.className = 'date-cell'; dayHeader.textContent = days[i]; daysHeader.appendChild(dayHeader); } calendar.appendChild(daysHeader); const firstDate = new Date(year, month, 1); const lastDate = new Date(year, month + 1, 0); for (let i = 1 - firstDate.getDay(); i <= lastDate.getDate(); i++) { const dateCell = document.createElement('div'); dateCell.className = 'date-cell'; if (i > 0 && i <= lastDate.getDate()) { dateCell.textContent = i; if (i === today) { dateCell.classList.add('today'); } } calendar.appendChild(dateCell); } } displayCalendar();

結果: 期待される表示は、現在月の曜日行と日付セルが生成され、今日の日付にtodayクラスが付く状態です。

サンプルコード15:入力チェック

入力チェックでは、フォーム送信の前に値を確認し、問題があれば送信を止めます。そのため、submitイベントでpreventDefaultを呼び、エラーがない場合だけform.submit()へ進めます。

HTML

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'><title>サンプルコード15:入力チェック</title><style>.error { color: red; }</style></head><body><form id='sample-form'><label for='username'>ユーザー名:</label><input type='text' id='username' name='username'><span class='error' id='username-error'></span><br><label for='email'>メールアドレス:</label><input type='text' id='email' name='email'><span class='error' id='email-error'></span><br><input type='submit' value='送信'></form><script src='script.js'></script></body></html>

結果: 期待される表示は、ユーザー名とメールアドレスの入力欄、エラー表示用のspan、送信ボタンが並ぶフォームです。

JavaScript(script.js)

const form = document.getElementById('sample-form'); function showError(elementId, message) { const errorElement = document.getElementById(elementId); errorElement.textContent = message; } function clearErrors() { showError('username-error', ''); showError('email-error', ''); } function validateForm(e) { e.preventDefault(); clearErrors(); const username = form.username.value.trim(); const email = form.email.value.trim(); let hasError = false; if (username === '') { showError('username-error', 'ユーザー名を入力してください。'); hasError = true; } if (email === '') { showError('email-error', 'メールアドレスを入力してください。'); hasError = true; } else if (!email.match(/^[^@]+@[^@]+.[^@]+$/)) { showError('email-error', '正しいメールアドレスを入力してください。'); hasError = true; } if (!hasError) { form.submit(); } } form.addEventListener('submit', validateForm);

結果: 期待される表示は、未入力や形式不一致のときに各入力欄の横へエラーメッセージが入り、問題がなければ送信へ進む状態です。

まとめ

JavaScriptライブラリは、繰り返し使う処理を名前付きの部品としてまとめる考え方です。その中心には、利用側が呼びやすいAPIを決め、内部処理を必要以上に外へ出さない設計があります。

作成方法では、オブジェクト形式、モジュールパターン、名前空間パターンを押さえると、小さな処理から複数機能を持つ部品まで整理できます。ただし、どの形でもnull確認、イベント解除、通信失敗、入力値の扱いを省くと、利用範囲が広がったときに不具合へつながりますが、これは押さえたい点です。

そのため、最初はDOM操作やイベント処理を小さくまとめ、慣れてきたらオプション、プラグイン、UI部品へ広げる流れが扱いやすいです。ライブラリを作る目的はコード量を減らすことだけでなく、同じ作法で機能を呼び出せる状態を作ることにあります。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。