iT邦幫忙

2025 iThome 鐵人賽

DAY 8
1

我個人很喜歡Butterfly主題一進去後就是一張大圖片很有震撼感,加上會出現打字特效顯示名言金句,所以打算將這個效果也套過來我的網站。

https://ithelp.ithome.com.tw/upload/images/20250903/20178433JVmQSngbtq.png

第 1 步|在 _config.kira.yml 加入設定

把這些東西直接加在最底部就行了

hero:
  enable: true
  image: /images/<圖片名稱>    # 首屏大圖
  title: <大標題>            # 不填會用 site.title
  typing: true               # true=打字特效;false=淡入/淡出輪播
  typingSpeed: 32             # 打字速度(毫秒/字;typing=true 才有效)
  rotateInterval: 4000        # 每句停留毫秒(typing=false 用)
  subtitles:                  # 你要輪播的句子(可放多句)
    - 我最擅長的就是一蹶不振。
    - 任何困難都能將我擊倒。

之後只改 subtitles 就能換輪播內容;想改速度、是否打字也都在這裡調。


加入隨機句

butterfly是使用Hitokoto這個網站進行隨機產生句子的,類似的網站也有其他可以自己選擇。

https://hitokoto.cn/

英文的一言網

https://zenquotes.io/

當然你也可以用歷史上的今天這種網站,有很多東西可以加,不過我只想用我自己的爛句子。

https://ithelp.ithome.com.tw/upload/images/20250903/20178433lgxwAsaaZ2.png

要用的話可以點進去那些一言網內有寫教學(之後可能會加在側邊吧,但首頁我只想放我自己的)

 #加上Hitokoto舉例
hero:
  enable: true
  image: /images/<圖片名稱>    # 首屏大圖
  title: <大標題>            # 不填會用 site.title
  typing: true               # true=打字特效;false=淡入/淡出輪播
  typingSpeed: 32             # 打字速度(毫秒/字;typing=true 才有效)
  rotateInterval: 4000        # 每句停留毫秒(typing=false 用)
  subtitles:                  # 你要輪播的句子(可放多句)
    - 我最擅長的就是一蹶不振。
    - 任何困難都能將我擊倒。
  hitokoto:                    # (可選)要的分類,留空代表全隨機
    categories: [a, b, c]      # a動畫 b漫畫 c遊戲 d文學 e原創 f網路 g其他 h影視 i詩詞 j諺語 k歌詞 l成語


第 3 步|建立注入腳本

建立資料夾與檔案:

mkdir -p scripts
touch scripts/home-hero.js

把下面整段貼進 scripts/home-hero.js

/* global hexo */
// 讀 hero 設定(優先 _config.kira.yml)
function getHeroCfg(ctx) {
  return (ctx.theme && ctx.theme.config && ctx.theme.config.hero) ||
         (ctx.config && ctx.config.hero) || {};
}
function esc(s=''){return String(s).replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m]));}

hexo.extend.filter.register('after_render:html', function (html, data) {
  if (!data || !data.path) return html;
  const isHome = String(data.path).replace(/\\/g,'/') === 'index.html';
  if (!isHome) return html;

  const cfg   = getHeroCfg(this);
  if (cfg.enable === false) return html;

  const img   = esc(cfg.image || '/images/hero.jpg');
  const title = esc(cfg.title || this.config.title || 'Welcome');
  const typing = !!cfg.typing;
  const speed  = Number(cfg.typingSpeed || 32);
  const hold   = Number(cfg.rotateInterval || 4000);
  const list   = Array.isArray(cfg.subtitles) && cfg.subtitles.length ? cfg.subtitles : ['Welcome.'];
  const json   = JSON.stringify(list);

  const headInject = `
<style id="home-hero-css">
  .hero-banner{
    position:relative;
    width:100vw;
    height:100svh;
    margin:0 0 28px;
    margin-left:calc(50% - 50vw);
    overflow:hidden;
  }
  .hero-bg{position:absolute;inset:0;background-size:cover;background-position:center;filter:brightness(.82)}
  .hero-bg::after{content:"";position:absolute;inset:0;background:rgba(0,0,0,.25)}
  .hero-inner{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;color:#fff;padding:0 1rem}
  .hero-title{font-weight:800;line-height:1.1;font-size:clamp(36px,5vw,64px);text-shadow:0 4px 24px rgba(0,0,0,.5)}
  .hero-sub-wrap{display:inline-flex;align-items:baseline;gap:.25rem;opacity:0;transition:opacity .6s ease}
  .hero-sub{margin-top:.75rem;font-size:clamp(14px,2vw,20px);opacity:.92;text-shadow:0 2px 12px rgba(0,0,0,.4);min-height:1.6em}
  .typed-cursor{display:${typing ? 'inline-block' : 'none'};margin-left:2px;opacity:1;animation:typed-cursor-blink .7s infinite}
  @keyframes typed-cursor-blink{0%,45%{opacity:1}46%,100%{opacity:0}}

  /* ↓ 向下箭頭按鈕(置底中) */
  .hero-down{
    position:absolute; left:50%; bottom:18px; transform:translateX(-50%);
    width:42px; height:42px; border-radius:9999px;
    border:2px solid rgba(255,255,255,.9);
    color:#fff; background:rgba(0,0,0,.18);
    display:flex; align-items:center; justify-content:center;
    cursor:pointer; transition:background .2s ease, transform .2s ease, opacity .2s ease;
    backdrop-filter:blur(2px);
    animation:hero-bounce 1.8s infinite;
  }
  .hero-down:hover{ background:rgba(0,0,0,.28); transform:translateX(-50%) translateY(2px); }
  @keyframes hero-bounce{ 0%,100%{ bottom:18px } 50%{ bottom:24px } }
  .hero-down svg{ display:block }
</style>`;

  const bodyInject = `
<div class="hero-banner" role="banner" aria-label="Site hero">
  <div class="hero-bg" style="background-image:url('${img}')"></div>
  <div class="hero-inner">
    <h1 class="hero-title">${title}</h1>
    <div class="hero-sub-wrap" id="hero-sub-wrap">
      <div class="hero-sub" id="hero-sub" aria-live="polite"></div>
      <span class="typed-cursor">|</span>
    </div>
    <!-- 向下箭頭 -->
    <button class="hero-down" id="hero-down" aria-label="捲動到內容">
      <svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
        <path fill="currentColor" d="M12 16.5L4.5 9l1.4-1.4L12 13.7l6.1-6.1L19.5 9z"/>
      </svg>
    </button>
  </div>
</div>
<script>
(function(){
  var items = ${json};
  var el = document.getElementById('hero-sub');
  var wrap = document.getElementById('hero-sub-wrap');
  var typing = ${typing ? 'true' : 'false'};
  var speed = ${speed};
  var hold = ${hold};
  var i = 0;

  function setOpacity(v){ wrap.style.opacity = v; }
  function fadeIn(cb){ requestAnimationFrame(function(){ setOpacity(1); if(cb) setTimeout(cb, 600); }); }
  function fadeOut(cb){ setOpacity(0); if(cb) setTimeout(cb, 600); }

  function typeText(text, done){
    el.textContent = '';
    var j = 0;
    (function tick(){
      if (j < text.length){
        el.textContent += text.charAt(j++);
        setTimeout(tick, speed);
      } else { if (done) done(); }
    })();
  }

  function show(idx){
    var text = String(items[idx]);
    if (typing){
      setOpacity(1);
      typeText(text, function(){ setTimeout(next, hold); });
    } else {
      el.textContent = text;
      fadeIn(function(){ setTimeout(function(){ fadeOut(next); }, hold); });
    }
  }

  function next(){
    i = (i + 1) % items.length;
    if (typing){ fadeOut(function(){ show(i); }); }
    else { show(i); }
  }

  if (!items.length) items = ['Welcome.'];
  if (typing){ setOpacity(0); fadeOut(function(){ show(0); }); } else { show(0); }

  // ↓ 點擊向下箭頭,平滑捲到下一個區塊
  // ↓ 點擊向下箭頭,平滑捲到 Hero 底部(自動扣固定導覽列)
var btn = document.getElementById('hero-down');
if (btn){
  btn.addEventListener('click', function(){
    var hero = document.querySelector('.hero-banner');
    if (!hero) return;

    // Hero 底部的頁面座標
    var y = hero.getBoundingClientRect().bottom + window.pageYOffset;

    // 如果有固定 header,就扣掉高度(常見 class/元素都掃過)
    var header = document.querySelector('.kira-header, header, .navbar, .site-header');
    if (header && getComputedStyle(header).position === 'fixed') {
      y -= header.offsetHeight || 0;
    }

    window.scrollTo({ top: Math.max(0, Math.round(y)), behavior: 'smooth' });
  });
}

})();
</script>`;

  html = html.replace('</head>', headInject + '\n</head>');
  html = html.replace('<body>', '<body>\n' + bodyInject);
  return html;
});

這樣就能有打字特效加上輪播句子(我最喜歡的就是模擬打字的特效)

以及有一個按鈕可以按一下就往下翻滾畫面

https://ithelp.ithome.com.tw/upload/images/20250903/20178433FyJ3OzKers.png

 #加上Hitokoto舉例
 // JSONP 取得 Hitokoto
  function jsonp(url, cb){
    var fn = 'jp_' + Math.random().toString(36).slice(2);
    window[fn] = function(d){ cb(d); delete window[fn]; s.remove(); };
    var s = document.createElement('script');
    s.src = url + (url.indexOf('?')>-1?'&':'?') + 'callback=' + fn;
    document.body.appendChild(s);
  }

  var toTW = (window.OpenCC && OpenCC.Converter) ? OpenCC.Converter({ from: 'cn', to: 'tw' }) : function(s){return s;};

  jsonp('${hitokotoUrl}', function(d){
    var s = (d && d.hitokoto) ? ('「' + d.hitokoto + '」 — ' + (d.from_who || d.from || '一言')) :
            (fallback.length ? rand(fallback) : 'Welcome.');
    s = toTW(s);   // 轉繁體
    start(s);
  });
})();
</script>`;

客製化小抄

  • 改高度:把 height:100svh 換成 82vh560px

  • 遮罩深淺:改 .hero-bg::afterrgba(0,0,0,.25)(越大越暗)

  • 只要版心寬度、不要全幅:把 .hero-banner

    width:100vw 改成 width:100%,並刪掉 margin-left:calc(50% - 50vw)

  • 字幕只顯示一行:在 .hero-subwhite-space: nowrap; overflow: hidden; text-overflow: ellipsis;


今天更新的東西感覺有點多,明天就輕鬆一點更新小東西吧


上一篇
DAY7 更改背景圖片
系列文
身為一個宅宅也想要有自己的小天地8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言