リンクのa要素を一時的に無効化するための実装

2026/08/10 (月) - 09:00 HTML

a要素のリンクに対し、一時的に遷移できないように設定したい場合。完全にリンクを消したい場合はaタグごと消すのが良いですが、仮リンクやキャンペーン実施前・終了後などに見た目的に一時的に無効化したい場合の実装方法。

HTMLの表記は以下の通り。CSSで見た目を制御するため任意のclass属性を付けて設定します。さらにtabindex="-1"aria-disabled="true"の属性を設定し、キーボード操作の無効化とアクセシビリティの振る舞いを設定します。

<a href="https://example.com/" class="is-disabled" tabindex="-1" aria-disabled="true">詳細はこちら!</a>

Tabキーで選択しEnterキーで遷移する…といったキーボード操作を無効化するために、tabindex="-1"を設定します。aria-disabled="true"を設定することで、この要素は無効であることを明示できます。

CSSは以下の通りで、クリックできないような振る舞いに設定します。デザイン部分は割愛。

.is-disabled {
 pointer-events: none; /* マウスクリック・タップを無効化 */
 cursor: default; /* カーソル形状をデフォルトに */
 opacity: .5; /* 視覚的に非活性を示す(任意) */
}

pointer-events: none;プロパティを指定することで、この要素はクリック・タップできないように設定できます。

これでマウスやタップの操作、および、キーボード操作両方から遷移を無効化できます。ただしくまで無効化にしてるだけなので、HTMLコードを見られたりするとリンク先がバレてしまいます。情報解禁前のリンクなどをあらかじめ埋め込んでおく…みたいな使い方は控えましょう。

WordPressの記事に「公開から○日経過」を表示する方法

2026/07/17 (金) - 09:00 Program

WordPressの記事詳細ページで、記事の公開日から今日までの経過日数を表示する方法。

ニュースや店舗やイベントの紹介情報、技術記事など、情報の鮮度が重要なコンテンツでは「公開から何日経過しているか」がひと目でわかると、読者が情報の新しさを判断しやすくなります。

また、一定期間が経過した記事に対して「公開から○年以上経過しており、情報が古い可能性があります」といった注意書きを表示すれば、古い情報をそのまま信頼してしまうリスクを減らすこともできます。

以下に、経過日数の表示方法と古い記事に注意メッセージを表示する実装方法をまとめて紹介します。

  • WordPress 6.x以上
  • PHP 8.x以上

まず、function.phpにロジック部分を実装します。

function get_post_age_notice() {
 $limit = 365; //古い記事の警告メッセージを出す日数 例だと365(1年)
 $post_date = get_the_date('Y-m-d');
 $publish_timestamp = get_the_time('U');
 $now_timestamp = current_time('timestamp');
 $diff_seconds = $now_timestamp - $publish_timestamp;
 $diff_days = floor($diff_seconds / DAY_IN_SECONDS);
 $diff_years = floor($diff_days / 365);
 if ($diff_years >= 1) {
  $age_text = sprintf('公開から%d年%d日経過しています', $diff_years, $diff_days % 365);
 } else {
  $age_text = sprintf('公開から%d日経過しています', $diff_days);
 }
 $output = '<div class="post-age-info">';
 $output .= '<p class="post-age-text">' . esc_html($age_text) . '</p>';
 if ($diff_days >= $limit) {
  $output .= '<p class="post-age-warning">⚠ この記事は公開から1年以上経過しており、情報が古い可能性があります。</p>';
 }
 $output .= '</div>';
 return $output;
}

記事詳細画面の任意の位置に経過日数と警告を表示します。可能な限り記事の上の方(記事タイトルの近く)に表示させ、すぐに気がつくような配慮が重要です。

<?php echo get_post_age_notice(); ?>

デザイン(CSS)は省略。ブログのイメージに合わせて任意で実装してください。

記事イメージ

実際の表示例。

パンくずリスト(トピックパス)をスマホでスクロールさせる

2026/06/19 (金) - 09:00 CSSHTML

中・大規模Webサイトで重要な役割を持つトピックパス、通称パンくずリスト。Webサイトの訪問者が「今サイト内のどの位置にいるか」を視覚的にわかりやすく示したテキストリンクのことで、名前の由来はグリム童話の「ヘンゼルとグレーテル」で、主人公の兄妹が森で迷子にならないよう通り道に「パンくず」を落として目印にしたエピソードからこの名が付けられました。

こういうやつです。

PCでは見やすくていいのですが、スマホだとページ上部で複数行に分断するなどで読みづらくなるので1行表示かつスクロールバーを出して収めたいと思います。

実装イメージ。

まずはパンくずリストのHTMLです。番号付きリストで実装しています。

<div class="topic-path">
 <ol>
  <li><a href="#">ホーム</a></li>
  <li><a href="#">会社情報</a></li>
  <li><a href="#">当社での取り組み</a></li>
  <li><em>サステナビリティ</em></li>
 </ol>
</div>

続いてCSSです。見た目の実装は割愛しています。

.topic-path ol{
 display: flex;
 flex-wrap: nowrap;
 overflow-x: auto;
 white-space: nowrap;
 list-style: none;
 gap: 5px;
}
.topic-path ol li{
 flex-shrink: 0;
 white-space: nowrap;
}

flex-boxで横並びを実現しつつ、子要素のliに対し flex-shrink: 0 を付けて縮めないようにしています。これで文字数やサイズが可変しても元サイズを保持したまま、overflow-x: auto によってスクロールできるようになります。

スクロールで要素が見えたら数値をカウントアップさせるギミックを実装する

2026/03/11 (水) - 09:00 JavaScript

LP(ランディングページ)やコーポレートサイトで実績の数値を表示する際、画面をスクロールして数字が現れた瞬間に、「0から目標値まで数字がパラパラと動く演出)」を見たことがあると思います。静止した数字よりも視覚的なインパクトが強く、実績やデータの重要性をよりダイナミックに表現できます。今回はそのギミックの実装です。

表示イメージ

まずはHTML要素の指定。span要素の数を増やせばその分カウントアップする要素を増やせます。

<p>従業員数:<span class="txtCount" data-count="2000">2000</span>人</p>

カウントアップさせたいHTML要素にdata-count属性を指定し、カウントアップさせたい上限の数値を指定します。また、SEOやJavaScriptが無効化されている環境、音声読み上げブラウザのために予め同じ数値をHTMLテキストとしても指定しておきます。

document.addEventListener('DOMContentLoaded', () => {
 function countUp(el){
  const duration = 500; //数字がカウントアップする時間を指定 (ミリ秒)
  const target = +el.getAttribute('data-count');
  const start = 0;
  const startTime = performance.now();
  const updateCount = (currentTime) => {
   const elapsed = currentTime - startTime;
   const progress = Math.min(elapsed / duration, 1);
   const currentNum = Math.floor(progress * (target - start) + start);
   el.textContent = currentNum.toLocaleString();
   if (progress < 1) {
    requestAnimationFrame(updateCount);
   } else {
    el.textContent = target.toLocaleString();
   }
  };
  requestAnimationFrame(updateCount);
 };
 const options = {
  threshold: 0.7 // 70%見えたら実行
 };
 const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
   if (entry.isIntersecting) {
    countUp(entry.target);
    observer.unobserve(entry.target);
   }
  });
 }, options);
 document.querySelectorAll('.txtCount').forEach(num => {
  observer.observe(num);
 });
});

JavaScriptのコード。IntersectionObserverを使い要素が見えたらカウントがアップするイベントを発火させます。durationでカウントアップさせる秒数をミリ秒で指定します。

例だと500ミリ秒(0.5秒)かけてカウントアップが完了するイメージです。

Next.js 16でキャッシュを管理する

2026/03/01 (日) - 09:00 JavaScript

Next.js16では、従来に比べコンポーネントのキャッシュ管理がより簡潔になり何がキャッシュされているかが分かりやすく、デバッグも容易になりました。ページのほか、コンポーネント、関数もキャッシュできるため、パフォーマンスのアップも期待できます。

PPR(Partial Prerendering)の導入

従来のNext.jsでは、SSRSSGISRで構成することが多かったですが、Next.js 16では新たにPPR(Partial Prerendering)が導入されました。これはページ内で静的部分と動的部分を融合でき、パフォーマンスを重視したページの構築が可能になります。

たとえばオウンドメディアなどでヘッダーやフッターは静的として構成し、記事一覧などを動的に管理することができます。ISRと異なりSEOやアクセシビリティの面で非常に有利です。

Cache Components(use cache ディレクティブ)

今回導入されたのがCache Components(キャッシュコンポーネント)です。各ページやコンポーネントにuse cacheを指定することで、対象となったファイルをキャッシュしてくれるようになります。デフォルトでは使えないため、任意で有効化する必要があります。

next.config.jscacheComponents: trueの設定値を加えます。

import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
 cacheComponents: true,
};
export default nextConfig;

そのうえでページやコンポーネントの先頭にuse cacheを指定すると、キャッシュされ静的として扱われます。

async function cacheComponent(){
 "use cache";
 return (
  キャッシュするコンポーネント
 );
}

関数でも同様です。例えばデータ取得のfetchなどもキャッシュすることでパフォーマンス向上を期待できます。

async function getPosts(){
 "use cache";
 const res = await fetch('https://example.com/posts');
 const posts = await res.json();
 return posts;
}

なお、PPRを使用してbuildするとPartial Prerenderとしてパブリッシュされます。

Route (app)         Revalidate  Expire
┌ ○ /              15m      1y
├ ○ /_not-found
└ ◐ /blog           15m      1y
○  (Static)             prerendered as static content
◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content

キャッシュの設定

cache機能のcacheLifeを利用することでキャッシュの有効期限なども設定できます。

import { cacheLife } from 'next/cache';
async function getPosts(){
 "use cache";
 cacheLife(days);
 const res = await fetch('https://example.com/posts');
 const posts = await res.json();
 return posts;
}

例だと1日キャッシュするようになります。細かくは公式ドキュメントを御覧ください。

PHPとChatGPT APIでよくある質問のチャットボット(Chatbot)っぽいものを作る

2025/12/01 (月) - 09:00 Program

商品やサービス案内のウェブサイト内に「よくある質問」や「FAQ」ページを設けることが多いと思います。最近はChatbotのソリューションもAIを使って優秀になりましたが、今回はPHPでChatGPT APIから自分のサイトの質問・回答データを参照して答える簡易的なChatbotを自作してみました。

ChatGPTがで質問内容を回答させた例

ChatGPTを利用することでサイト内検索などを使わなくても、会話感覚で自然な情報検索が実現できます。

今回はカフェバーのよくある質問の検索ページを作る想定とします。参照するデータとして予め質問・回答データを集約したJSONファイルを用意しておきます。テキストやCSVでも構いませんし、直接SQLを叩いて呼び出しても構いません。

[
 {
 "question": "営業時間",
 "answer": "営業時間:11:00~23:00。定休日:年中無休です。"
 },
 {
 "question": "喫煙",
 "answer": "店内は全席禁煙になっております。"
 },
 〜略〜
 {
 "question": "飲酒",
 "answer": "ビール、ハイボール、サワー、シャンパン、ワイン、日本酒などのアルコール各種を豊富に用意しております。100種類のドリンクからお選び頂けます。"
 },
 {
 "question": "お会計",
 "answer": "現金、クレジットカード(VISA、MATER、JCB、AMEX、ダイナース、Discover)、電子決済(paypay、楽天Pay、LINE Pay、au Pay、WeChatPay、AliPay)がご利用いただけます。"
 }
]

次にPHPで、JSONファイルを参照してGPT APIが質問を受けるプログラムを実装します。実装する前に予めOpenAI developer platformからAPI Keysを取得しておきましょう。プロンプトは適当に変えてください。そして入力フォームからPOSTで送信された質問内容を取得して処理し、処理した質問文をプロンプトとしてAPIにリクエストします。

define('API_KEY', '取得したAPIキーを指定');
define('API', 'https://api.openai.com/v1/chat/completions');
function findFAQ($userQuestion) {
 $faqData = json_decode(file_get_contents('JSONファイルのURL'), true);
 $faqText = '';
 foreach ($faqData as $faq) {
  $faqText .= "Q: {$faq['question']}\nA: {$faq['answer']}\n\n";
 }
 $faqtext = "以下はFAQデータです。ユーザーからの質問に最適な回答を返してください。\n語尾ににゃんを付けて可愛く喋ってください。\nFAQデータ:${faqText}\n\nユーザーの質問: ${userQuestion}\n答え:";
  $prompt = [
   [
    'role' => 'user',
    'content' => $faqtext
   ]
  ];
  $url = curl_init(API);
  $header = array(
   'Authorization: Bearer '.API_KEY,
   'Content-type: application/json',
  );
  $params = json_encode([
   'messages' => $prompt,
   'model' => 'gpt-3.5-turbo',
  ]);
  $options = array(
   CURLOPT_POST => true,
   CURLOPT_HTTPHEADER =>$header,
   CURLOPT_POSTFIELDS => $params,
   CURLOPT_RETURNTRANSFER => true,
  );
  curl_setopt_array($url, $options);
  $httpResponse = curl_exec($url);
  $httpCode = curl_getinfo($url,CURLINFO_RESPONSE_CODE);
  if($httpCode === 200){
   $jsonArray = json_decode($httpResponse, true);
   $Answer = nl2br($jsonArray['choices'][0]['message']['content']);
  }else{
   $Answer = "申し訳ありませんにゃん。現在検索が使えないにゃん。";
  }
  return $Answer;
  curl_close($ch);
}
$question = htmlspecialchars($_POST['question'],ENT_QUOTES,'UTF-8') ?? '';
if ($question) {
 $answer = findFAQ($question);
} else {
 $answer = "質問を入力してにゃん♪";
}

正常にレスポンスが返ってきたらその値をフロントに表示します。質問文が空欄だったり、APIから正常にレスポンスが受け取れなかった場合はエラーを表示します。デザインはHTMLやCSSで適当に整形してください。

<form method="post">
 <dl>
  <dt><label for="question">質問</label></dt>
  <dd><input type="text" id="question" name="question" placeholder="質問文を入れてにゃん!" /></dd>
 </dl>
 <input type="submit" value="質問する" />
</form>
<hr />
<?php if ($userQuestion){ ?>
 <h2>質問:<?php echo $userQuestion; ?></h2>
 <p>回答:<?php echo nl2br(htmlspecialchars($answer)); ?></p>
<?php } ?>

なお、本格的なXSSなどのセキュリティ対策や、正常に回答が得られなかった場合の模範回答対策などは省いております。答えられなかった場合は問い合わせページへ誘導する処理などを適宜実装してください。

おさわり禁止

よくある質問ページのチャットボット(Chatbot)もどきを導入することでストレスなくユーザーの悩み解決を解決をサポートしし、問い合わせの人的な対応工数を削減できます。ただし、APIにリクエストする度にOpenAIに課金されてしまうので、その部分のコストはご注意ください。

JSで文字をシャッフルしながら1文字ずつ出現させる

2025/09/12 (金) - 10:00 JavaScript

見出しやナビの文字をランダムな文字でシャッフルしながら1文字ずつ出現するやつをJavaScriptで作りました。

昔、ActionScript 3.0(AS3)で作ったライブラリを無理矢理JavaScriptに書き換えた物なので、綺麗なコードではありませんが…。使い方としては、あらかじめHTML要素に文字を用意しておき、シャッフルテキスト用テキスト要素を指定したインスタンスを生成します。その後よきタイミング(ボタンを押下したり、スクロールが動いたタイミングなど)でplay();メソッドを実行すると対象のテキストがシャッフルします。

見出しなどあくまで短い文章向けのエフェクトなので、本文などに指定するのは避けるべきです。

HTMLのコード

<p id="txt">Chu!可愛くてごめん</p>
<button type="button" id="btn">シャッフルする</button>

実行させるためのJavaScript

const txt = document.getElementById('txt');
const btn = document.getElementById('btn');
const shuffle = new shuffleTxt(txt);
btn.addEventListener('click',function(){
 shuffle.play(); //ボタンを押下したら文字をシャッフル
});

本体のコード

const fps = 60; //インターバル 小さい値にすると遅くなる
class shuffleTxt{
 constructor(target){
  this.isFlg = false;
  this.targetEl = target;
  this.strBase = this.targetEl.textContent;
  this.strRandom = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?#$%&|/*+-=;_:'
  this.strComplete = '';
  this.intRemaining= this.strBase.length;
  this.intNowCnt = 0;
  this.intStartTime = 0;
  this.intNowTime;
  this.intFPS = 1000/fps;
  this.timeID;
 }
 play(){
  if(this.isFlg){
   return false;
  }else{
   this.isFlg = true;
   this.targetEl.textContent = '';
   this.searchTxt();
  }
 }
 stop(){
  this.intNowCnt = 0;
  this.intStartTime = 0;
  this.intNowTime = 0;
  this.intRemaining = this.strBase.length;
  this.targetEl.textContent = this.strBase;
  this.isFlg = false;
 }
 searchTxt(){
  let difTime;
  if(this.strBase.length >= this.intNowCnt){
   this.strComplete = this.strBase.substr(0,this.intNowCnt);
   this.timeID = requestAnimationFrame(() => {
    this.intNowTime = new Date().getTime();
    difTime = this.intNowTime - this.intStartTime;
    if ( difTime >= this.intFPS ) {
     this.intStartTime = this.intNowTime;
     this.randomTxt();
    }else{
     this.searchTxt();
    }
   }); 
  }else{
   cancelAnimationFrame(this.timeID);
   this.stop();
  }
 }
 randomTxt(){
  let str = '';
  for (let i=0; i<=this.intRemaining; i++){
   str += this.strRandom.charAt(Math.floor(Math.random()*this.strRandom.length))
  }
  this.targetEl.textContent = this.strComplete + str;
  this.intNowCnt++;
  this.intRemaining--;
  this.searchTxt();
 }
}

Reactで画像がクロスフェードするスライドショーを作る

2025/08/19 (火) - 09:00 JavaScript

Reactで複数枚の画像をクロスフェードしてループで切り替えるスライドを簡易的に作ってみる。

スライドショーイメージ

JavaScriptのコードは以下の通り。予め画像を用意しておきそれを1枚ずつ切り替える想定です。ポイントとしては画像のクロスフェードを制御をCSS側で制御することで、class属性の有無で表示・非表示を切り替えます。

import React, { useState, useEffect } from 'react';
import './ImageSlider.css';
// 画像の枚数とファイル名を配列で指定
const images = [
 'slide1.webp',
 'slide2.webp',
 'slide3.webp'
];
function ImageSlider() {
 const [currentIndex, setCurrentIndex] = useState(0);
 useEffect(() => {
  // setIntervalで3秒ごとに画像を切り替えるタイマーを設定
  const timerId = setInterval(() => {
   setCurrentIndex(prevIndex => (prevIndex + 1) % images.length);
  }, 3000);
  return () => clearInterval(timerId);
 }, []);
 return (
  <div className="imgSlide">
   {images.map((image, index) => (
    <img
     key={index}
     src={image}
     alt={`画像No:${index + 1}`}
     className={`imgSlideItem ${currentIndex === index ? 'isActive' : ''}`}
     width={640}
     height={480}
    />
   ))}
  </div>
 );
}
export default ImageSlider;

CSSは以下の通りで画像の表示制御をします。transitionプロパティで透明度のスピードなどを制御します。標準では画像の透明度のopacityプロパティを0で不可視にしておき、アクティブになったらclass属性を付与し可視させます。

.imgSlide{
 position: relative;
 width: 640px;
 height: 480px;
 overflow: hidden;
}
.imgSlideItem{
 display: block;
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 object-fit: cover;
 opacity: 0; /* 非アクティブ時は画像の透明度を0 */
 transition: opacity 1s ease-in-out; /* 1秒かけてフェードイン・アウト */
}
.imgSlideItem.isActive{
 opacity: 1; /* アクティブ時は画像の透明度を1 */
}

コンポーネント化して任意の位置に埋め込むのがいいでしょう。

特定のHTML要素以外をクリックした時にイベントを発火

2025/08/13 (水) - 09:00 JavaScript

Webページで特定の要素をクリックした時と、それ以外の要素をクリックした時とで処理を分岐したい…。例えば親以上の要素にイベントハンドラを設定してしまうと、自身の要素もイベントハンドラの対象になってしまうのでそれを防ぎたい。

モーダルウィンドウなどで指定した要素以外をクリックしたらイベントを発火させたい場合は以下のように実装します。closestメソッドを使うと指定したセレクタ自身、または最も近い親要素を取得できます。targetと組み合わせて指定したセレクタが含まれなかったら〜で判別します。HTMLの例は以下の通り。

<div id="modal">モーダルウィンドウ</div>

JavaScript

document.addEventListener('click', function(e){
 if(!e.target.closest('#modal')) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

jQueryで似たようなことを再現する場合

$(document).on('click',function(e) {
 if(!$(e.target).closest('#modal').length) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

Reactで似たようなことを再現する場合

useRefを使うことでDOMを参照し、イベントハンドラを実行できるようにします。

import React, { useEffect, useRef } from 'react';
function App() {
 const modalRef = useRef<HTMLDivElement | null>(null);
 useEffect(() => {
  const handleClickOutside = (e: MouseEvent) => {
   if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
    console.log('モーダル以外をクリック!');
   } else {
    console.log('モーダルをクリック!');
   }
  };
  document.addEventListener('click', handleClickOutside);
 }, []);
 return (
  <div id="modal" ref={modalRef}>モーダルウィンドウ</div>
 );
}
export default App;

これを使うことで例えば、モーダルウィンドウやドロップダウンメニュー以外の外側をクリックしたら要素を非表示にする…など実現できます。

Movable Type(MT)で年ごと分割した月別アーカイブリストを実装

2025/08/01 (金) - 09:00 Program

Movable Type(MT)で以下のように年度ごとで見出しを分け、月ごとのアーカイブリストを作成する場合の実装方法。

Movable Type実装イメージ

環境は以下の通り。

  • Movable Type 8.x

実装するため、予め月別アーカイブを出力するように設定したうえでMovable Typeのデザインテンプレートの任意のモジュール等に以下のコードを入れて再構築する。

<MTSetVar name="current_year" value="0">
<MTArchiveList archive_type="Monthly" sort_order="descend">
 <MTSetVarBlock name="entry_year"><MTArchiveDate format="%Y"></MTSetVarBlock>
 <MTIf name="entry_year" ne="$current_year">
  <MTIf name="current_year" ne="0">
</ul>
  </MTIf>
<h2><MTArchiveDate format="%Y年"></h2>
<ul>
 <MTSetVar name="current_year" value="$entry_year">
 </MTIf>
 <li><a href="<MTArchiveLink>"><MTArchiveDate format="%Y年%m月"></a></li>
 <MTIf name="__last__">
</ul>
 </MTIf>
</MTArchiveList>

マークアップやデザインはサイトに合わせて調整すること。

ページの先頭へ