Bloggerにクリックするとスカイランタンが浮かぶエフェクトに変えてみた

クリックの瞬間に、波紋の代わりにスカイランタンがぽこっと出現。 幻想的で、画面にやさしい遊び心を添えるエフェクトです。
alt_here

色々気になった

テーマ変更後のチェックをしていて……気になってきた画面のエフェクト

ライトモード ダークモード
画面クリック シャボン玉ふわり シャボン玉ふわり
背景 時々流れ星ヒュン

ダークモードを夜空に見立てて 時々キラッヒュンと流れ星
星空とか流星群とかにしないで あえて抑えた感じにしていたけど……物足りない?

夜空にもシャボン玉
きれいだったけど……僕のシャボン玉のイメージは青空の下?

ダークモードを変えてみた

最終的にはこんな感じ

ライトモード ダークモード
画面クリック シャボン玉ふわり スカイランタンふわり
背景 スカイランタンふわりふわり

ライトモードは そのままシャボン玉

ダークモードを 流れ星とシャボン玉の代わりに スカイランタンに
イメージはディズニー映画 塔の上のラプンツェル そのランタンフェスティバル

流石に映画みたいな数のランタンだと重くなるので……今は最多34個かな? でも雰囲気出てるー

うーん……良き(˶ᵔ ᵕ ᵔ˶)

コードも載せてみました
もしよろしければどうぞ くれぐれもバックアップはお忘れなく

(NOTEにまとめてありますが)

置くコードは3つ

CSS → ランタンの見た目
BG JS → 背景ランタン
TAP JS → クリック演出

ちなみに シャボン玉のCSSはほぼ無くした らしい

  • 変更前のシャボン玉(CSS/DOM版)

    .bubble を作って
    * ガラスっぽい縁
    * backdrop-filter: blur(4px) の屈折感
    * mix-blend-mode: screen の発光
    * ::after の白い反射
    * .particle の破裂
    まで付いてた かなり装飾が多い豪華版

  • 変更後のシャボン玉(Canvas版)

    Canvas で
    * 円を描く
    * カラフルな radial-gradient
    * 外周stroke
    * ふわっと上昇
    * 透明フェード

    破裂パーティクルは無い
    白い反射も無い
    ガラスの屈折感 も弱い

    でも見た目は幻想的 / 軽い / 滑らか

ダークモード用の判定クラス2箇所を変更してください

下記のMulti-tabs Code Blockは「drK」
(BG JSの16行目付近)
(TAP JSの37行目付近)

/* =========================
   🏮 Sky Lantern Pack
   Rapunzel Night ver.
========================= */

.sky-lantern,
.hero-lantern{
  position: fixed;
  left: 0;
  top: 0;
  pointer-events: none;
  opacity: 0;
  transform-origin: center center;
  will-change: transform, opacity;
  overflow: visible;
}

/* 背景 */
.sky-lantern{
  z-index: 999999;
  animation: lanternFloat linear forwards;

  /* 背景だけ少し濃いぽわ */
  box-shadow:
    0 0 16px rgba(255,215,120,.16),
    0 0 40px rgba(255,170,70,.10);
}

/* 主役 */
.hero-lantern{
  z-index: 1000000;
  width: 32px;
  height: 44px;
  animation: heroLanternFloat 12s ease-in-out forwards;
}

/* ===== 縦長 ===== */
.sky-lantern.tall,
.hero-lantern.tall{
  border-radius: 16% 16% 12% 12% / 8% 8% 10% 10%;
}

/* ===== 平たい ===== */
.sky-lantern.flat,
.hero-lantern.flat{
  border-radius: 20% 20% 14% 14% / 12% 12% 10% 10%;
}

/* ===== 真円 + 底カット ===== */
.sky-lantern.round,
.hero-lantern.round{
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
}

/* 本体 */
.sky-lantern::before,
.hero-lantern::before{
  content:"";
  position:absolute;
  inset:0;
  border-radius:inherit;

  background:
    linear-gradient(
      to bottom,
      rgba(182,95,28,.62) 0%,
      rgba(226,132,46,.74) 34%,
      rgba(255,192,92,.88) 68%,
      rgba(255,236,170,.98) 100%
    );

  box-shadow:
    0 0 10px rgba(255,210,130,.20),
    0 0 28px rgba(255,170,70,.12),
    0 0 70px rgba(255,140,50,.06);

  filter: blur(.2px);
}

/* 底面リング発光 */
.sky-lantern::after,
.hero-lantern::after{
  content:"";
  position:absolute;
  left:12%;
  right:12%;
  bottom:4%;
  height:16%;
  border-radius:50%;

  background:
    radial-gradient(
      ellipse at center,
      rgba(255,255,235,.98) 0%,
      rgba(255,236,160,.92) 28%,
      rgba(255,180,70,.55) 58%,
      rgba(255,120,40,0) 100%
    );

  box-shadow:
    0 0 14px rgba(255,242,190,.34),
    0 0 38px rgba(255,188,82,.18),
    0 0 72px rgba(255,160,62,.08);
}

/* 縦長 / 平たい */
.sky-lantern.tall::before,
.hero-lantern.tall::before,
.sky-lantern.flat::before,
.hero-lantern.flat::before{
  clip-path: polygon(
    6% 0%,
    94% 0%,
    100% 82%,
    86% 100%,
    14% 100%,
    0% 82%
  );
}

/* 丸 */
.sky-lantern.round::before,
.hero-lantern.round::before{
  clip-path: polygon(
    0% 45%,
    6% 18%,
    18% 4%,
    82% 4%,
    94% 18%,
    100% 45%,
    96% 84%,
    82% 100%,
    18% 100%,
    4% 84%
  );
}

/* アニメーション */
@keyframes lanternFloat{
  0%{
    opacity:0;
    transform:translate3d(0,0,0) scale(.75);
  }
  8%{
    opacity:.96;
  }
  100%{
    opacity:0;
    transform:
      translate3d(var(--dx),var(--dy),0)
      scale(.20)
      rotate(var(--rot));
  }
}

@keyframes heroLanternFloat{
  0%{
    opacity:0;
    transform:translate3d(0,0,0) scale(.9);
  }
  8%{
    opacity:1;
  }
  100%{
    opacity:0;
    transform:
      translate3d(var(--dx),var(--dy),0)
      scale(.38)
      rotate(var(--rot));
  }
}
  
/* =========================
   🏮 Sky Lantern Pack
   Background Lantern JS
========================= */
<script>
/*<![CDATA[*/
(function(){
  let timer = null;
  let startedAt = 0;
  let activeLanterns = 0;
  let clusterX = null;

  const container = document.body;

  function isDarkMode(){
    return document.documentElement.classList.contains("drK");
  }

  function rand(min, max){
    return Math.random() * (max - min) + min;
  }

  /* 密集ポイント */
  function spawnX(){
    if (clusterX !== null && Math.random() < 0.72){
      return Math.max(4, Math.min(96, clusterX + rand(-9, 9)));
    }
    return rand(4, 96);
  }

  function createLantern(){
    if (!isDarkMode()) return;

    const el = document.createElement("div");

    /* 四角70 / 丸30 */
    if (Math.random() < 0.7){
      el.className = "sky-lantern tall";
    } else {
      el.className = "sky-lantern round";
    }

    /* 位置 */
    const x = spawnX();
    const y = window.innerHeight + rand(40, 200);

    el.style.left = x + "vw";
    el.style.top = y + "px";

    /* 小型化 */
    const size = rand(12, 22);
    el.style.width = size + "px";
    
    if (el.classList.contains("round")) {
    /* 完全な球体 */
    el.style.height = size + "px";
    } else {
    /* 縦長 */
    el.style.height = (size * 1.35) + "px";
}

    /* ゆらぎ */
    el.style.setProperty("--dx", rand(-90, 90) + "px");
    el.style.setProperty("--dy", -(window.innerHeight + rand(280, 760)) + "px");
    el.style.setProperty("--rot", rand(-5, 5) + "deg");

    /* ゆっくり */
    const duration = rand(26, 44);
    el.style.animationDuration = duration + "s";

    /* 色味C */
    if (Math.random() < 0.2){
      el.style.filter =
        "hue-rotate(-10deg) saturate(.74) brightness(.92)";
    } else {
      el.style.filter =
        "hue-rotate(" + rand(-6, 8) + "deg) saturate(1.06) brightness(1.04)";
    }

    container.appendChild(el);
    activeLanterns++;

    setTimeout(() => {
      if (el.parentNode) el.remove();
      activeLanterns = Math.max(0, activeLanterns - 1);
    }, duration * 1000 + 100);
  }

  function loop(){
    if (!isDarkMode()) return;

    const elapsed = (Date.now() - startedAt) / 1000;

    /* ゆっくり増える */
    let target = 0;

    if (elapsed < 6){
      target = 0;
    } else if (elapsed < 14){
      target = 1;
    } else if (elapsed < 24){
      target = 2;
    } else if (elapsed < 38){
      target = 6;
    } else if (elapsed < 55){
      target = 14;
    } else if (elapsed < 75){
      target = 24;
    } else {
      target =
        34 +
        Math.floor(Math.sin(elapsed / 10) * 6) +
        Math.floor(rand(-2, 6));
    }

    /* 密集ポイント更新 */
    if (Math.random() < 0.10){
      clusterX = rand(15, 85);
    }

    /* 補充 */
    if (activeLanterns < target){
      const add = Math.min(
        target - activeLanterns,
        1 + Math.floor(Math.random() * 3)
      );

      for (let i = 0; i < add; i++){
        setTimeout(createLantern, i * 300);
      }
    }

    /* まとまり上昇 */
    if (elapsed > 55 && Math.random() < 0.20){
      const burst = 2 + Math.floor(Math.random() * 4);

      for (let i = 0; i < burst; i++){
        setTimeout(createLantern, i * 240);
      }
    }
  }

  function startLanterns(){
    if (timer) return;

    startedAt = Date.now();
    activeLanterns = 0;
    clusterX = null;

    timer = setInterval(loop, 1000);
  }

  function stopLanterns(){
    if (!timer) return;

    clearInterval(timer);
    timer = null;

    document
      .querySelectorAll(".sky-lantern")
      .forEach(el => el.remove());

    activeLanterns = 0;
    clusterX = null;
  }

  document.addEventListener("DOMContentLoaded", () => {
    if (isDarkMode()) startLanterns();
  });

  const mo = new MutationObserver(() => {
    if (isDarkMode()) startLanterns();
    else stopLanterns();
  });

  mo.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ["class"]
  });

  window.addEventListener("beforeunload", stopLanterns);
})();
/*]]>*/
</script>

  
/* =========================
   🏮 Sky Lantern Pack
   Tap Effect JS
   Light = Bubble
   Dark  = Hero Lantern
========================= */
<script>
/*<![CDATA[*/
(function(){

  /* ---- Bubble Canvas Setup ---- */
  let bubbleCanvas = document.createElement("canvas");
  bubbleCanvas.id = "bubble-canvas";
  bubbleCanvas.style.position = "fixed";
  bubbleCanvas.style.top = 0;
  bubbleCanvas.style.left = 0;
  bubbleCanvas.style.pointerEvents = "none";
  bubbleCanvas.style.zIndex = 9999;

  document.body.appendChild(bubbleCanvas);

  let ctx = bubbleCanvas.getContext("2d");
  let bubbles = [];
  let w, h;

  /* ---- Resize ---- */
  function resizeCanvas(){
    w = bubbleCanvas.width = window.innerWidth;
    h = bubbleCanvas.height = window.innerHeight;
  }

  resizeCanvas();
  window.addEventListener("resize", resizeCanvas);

  /* ---- Dark Mode ---- */
  function isDarkMode(){
    return document.documentElement.classList.contains("drK");
  }

  /* ---- Bubble ---- */
  function createBubble(x, y){
    bubbles.push({
      x,
      y,
      radius: 5 + Math.random() * 20,
      opacity: 0.4 + Math.random() * 0.4,
      speedX: -1 + Math.random() * 2,
      speedY: -1 - Math.random() * 1.5,
      life: 1
    });
  }

  /* ---- Hero Lantern ---- */
  function createHeroLantern(x, y){
    const el = document.createElement("div");
    el.className = "hero-lantern tall";

    el.style.left = (x - 16) + "px";
    el.style.top = (y - 22) + "px";
    el.style.opacity = "0";

    const dx = (-40 + Math.random() * 80);
    const dy = -(window.innerHeight * 0.75 + Math.random() * 160);
    const rot = (-6 + Math.random() * 12);

    el.style.setProperty("--dx", dx + "px");
    el.style.setProperty("--dy", dy + "px");
    el.style.setProperty("--rot", rot + "deg");

    document.body.appendChild(el);

    /* 点灯 */
    el.animate([
      {
        opacity: 0,
        transform: "scale(.7)"
      },
      {
        opacity: 1,
        transform: "scale(1)"
      }
    ], {
      duration: 500,
      easing: "ease-out",
      fill: "forwards"
    });

    /* 0.5秒後に上昇 */
    setTimeout(() => {
      el.style.animationPlayState = "running";
      el.classList.add("fly");
    }, 500);

    /* remove */
    setTimeout(() => {
      if (el.parentNode) el.remove();
    }, 10000);
  }

  /* ---- Bubble Animation ---- */
  function animate(){
    ctx.clearRect(0, 0, w, h);

    bubbles.forEach((b, i) => {
      b.x += b.speedX;
      b.y += b.speedY;
      b.life -= 0.005;

      if (b.life <= 0){
        bubbles.splice(i, 1);
        return;
      }

      ctx.beginPath();
      ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);

      ctx.strokeStyle = `rgba(173,216,230,${1.55 * b.life})`;
      ctx.lineWidth = 2.8;

      const hue = Math.floor(Math.random() * 360);

      const gradient = ctx.createRadialGradient(
        b.x - b.radius / 3,
        b.y - b.radius / 3,
        1,
        b.x,
        b.y,
        b.radius
      );

      gradient.addColorStop(
        0,
        `hsla(${hue},90%,100%,${b.opacity})`
      );
      gradient.addColorStop(
        0.4,
        `hsla(${hue + 50},90%,70%,0.35)`
      );
      gradient.addColorStop(
        0.7,
        `hsla(${hue + 120},80%,60%,0.25)`
      );
      gradient.addColorStop(
        1,
        `rgba(173,216,230,0)`
      );

      ctx.fillStyle = gradient;
      ctx.fill();
      ctx.stroke();
    });

    requestAnimationFrame(animate);
  }

  animate();

  /* ---- Click ---- */
  window.addEventListener("click", (e) => {

    /* dark */
    if (isDarkMode()){
      createHeroLantern(e.clientX, e.clientY);
      return;
    }

    /* light */
    createBubble(e.clientX, e.clientY);

    const sound = document.getElementById("pop-sound");
    if (sound){
      sound.currentTime = 0;
      sound.play().catch(() => {});
    }

  }, { passive:true });

})();
/*]]>*/
</script>

  
/* =========================
   🏮 Sky Lantern Pack NOTE
========================= */

Mode:
- Light Mode = Bubble Canvas 🫧
- Dark Mode  = Sky Lantern 🏮

Dark Switch:
- 判定 class = drK
- true  → dark effect
- false → light effect

Install:
- CSS    → <style> 内
- BG JS  → </body> 前の script
- TAP JS → </body> 前の script

Customize:
[CSS]
- 背景ぽわ強さ
  .sky-lantern → box-shadow

- 底の灯り位置
  ::after → bottom: 4%

- 色味
  ::before → linear-gradient

- 丸さ
  .round / clip-path

[BG JS]
- 数
  loop() の target

- 速度
  duration = 26〜44s

- 密集感
  spawnX()
  clusterX

- 比率
  tall 70%
  round 30%

[TAP JS]
- 点灯待ち
  500ms

- 主役サイズ
  left/top 補正
  hero size

Memo:
Safariが重くなる原因
↓
× large blur
× multiple drop-shadow
× huge glow stack

軽くて綺麗
↓
○ small shadow
○ thin glow
○ moderate count
  
いいね!と思ったらお願いします。。
人気ブログランキング

コメントを投稿

Laugh away - YUI