星期一, 12月 01, 2025

[CSS] 三欄流體式設計

延續 [CSS] Flex 基礎練習 練習內容,該筆記要記錄三欄顯示會隨著瀏覽器縮小而變成兩欄、一欄,模擬在不同行動裝置上的 UI 顯示,會用上重點為
  • box-sizing:預設為 content-box,設定為 border-box 方便計算和應用
  • max-width:以父元素 (上一層) 寬度為判斷目標
    • 當父元素寬度高於設定值 (比較寬) 時:保持該寬度
    • 當父元素寬度低於設定值 (比較窄) 時:隨父元素寬度自動調整
  • flex-wrap:預設為 no-wrap,會設定 wrap,隨著瀏覽器縮小而折行
  • Media Query:判斷瀏覽器大小
    • 大螢幕:三欄 (約 1024px 以上)
    • 中螢幕:兩欄 (約 768px 到 992px 之間)
    • 小螢幕:一欄 (約 767px 以下)
Index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>[CSS] 三欄流體式設計</title>
</head>

<body>
    <div class="wrap">
        <div class="content">
            <div class="item">
                <img src="https://82534363_4f518a102e_n.jpg" alt="高雄愛河">
                <h2>高雄愛河</h2>
                <p>高雄愛河是穿梭都市的生命之水,兩岸綠樹成蔭,微風徐徐。夜間尤其迷人,河畔燈光倒映水面,波光粼粼,如同一條璀璨的銀帶。遊客可漫步親水步道,或搭乘愛之船,感受迎面而來的浪漫氣息。週末更有市集和街頭藝人,為這片靜謐的河景增添熱鬧與活力,是高雄最具代表性的休憩與觀光勝地。</p>
            </div>
            <div class="item">
                <img src="https://1776884718_0ed0417709_n.jpg" alt="溫哥華市中心">
                <h2>溫哥華市中心</h2>
                <p>溫哥華市中心坐擁壯麗的海灣景色,現代化的玻璃帷幕高樓直插雲霄,與湛藍的英吉利灣和布拉德內灣形成鮮明對比。街道充滿活力,從時尚的羅布森街到歷史悠久的蓋士鎮,處處可見繁忙的購物人潮、多元的餐飲文化。市區緊鄰史丹利公園,讓這座都會在繁華之餘,仍保有親近大自然的綠色魅力。</p>
            </div>
            <div class="item">
                <img src="https://125408140_c9204a223e_n.jpg" alt="台北大直橋">
                <h2>台北大直橋</h2>
                <p>台北大直橋是橫跨基隆河的一座醒目地標,以其獨特的白色拱形鋼纜設計而聞名,流線型的橋身優雅地劃過水面,極具現代感。夜幕降臨後,橋體會亮起多變的燈光秀,與周圍的內湖科技園區和美麗華摩天輪夜景相互輝映,是攝影愛好者的熱點。它不僅是重要的交通樞紐,更是一座融合工程與藝術的城市景觀橋。</p>
            </div>
        </div>
    </div>
</body>
</html>
style.css
*{
    box-sizing: border-box; /* 讓 padding 和 border 包含在 width 內 */
}

.wrap{
    max-width: 1024px;
    margin: 0 auto;
}

.content{
    display: flex;
    justify-content: center;
    gap: 10px; /* item 之間的間距 */
    flex-wrap: wrap; /* 允許折行 */
}

.item{
    width: 30%;
    padding: 10px;
    border: lightgray 1px solid;
}

.item img{
    width: 100%;
}

.item h2{
    text-align: center;
    font-size: 25px;
}

/* 中螢幕 */
@media (max-width:992px) {
	.item{
		width: 48%;
        margin-bottom: 10px;
	}
}

/* 小螢幕 */
@media (max-width:767px) {
	.item{
		width: 80%
	}
}
效果呈現

星期一, 11月 24, 2025

[CSS] CSS 基礎練習

課程練習,說明如下
  • CSS Reset:使用 CSS Tools: Reset CSS
  • 使用 div 來進行排版:網頁水平置中
  • header:一張圖片(img)
  • body:
    • 使用後代選擇器來進行設定
    • p 段落:兩個段落,顏色為藍色,使用 Lorem 快速產生文字
    • 留白:使用 padding (往內推)、margin (往外推)
    • a 連結:修正為塊狀 (display:inline -> display:block) 且滑鼠滑過去後背景變成黑色
  • footer:
    • 文字置中
index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>[CSS] CSS 基礎練習</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <div class="page">        
        <div class="header"><img src="https://54725596729_d130df08f7_n.jpg" alt="Blog logo"></div>

        <div class="body">
            <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quia nemo alias magnam perferendis saepe rem,
                voluptatum quaerat neque similique vero obcaecati quas a vitae commodi laboriosam earum animi provident
                nulla.</p>

            <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quia nemo alias magnam perferendis saepe rem,
                voluptatum quaerat neque similique vero obcaecati quas a vitae commodi laboriosam earum animi provident
                nulla.</p>

            <a href="https://jengting.blogspot.com/" target="_blank">~楓花雪岳~</a>
        </div>

        <div class="footer">地址:OO 市 XX 區 OO 路 XX 號</div>
    </div>
</body>
</html>
style.cs
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

.page {
    width: 800px;
    margin-left: auto;
    margin-right: auto;
}

.header {
    border: 5px solid lightgray;
    margin: auto;
    padding: 10px;
    ;
}

.body {
    border: 5px solid red;
    margin: auto;
    padding: 10px;
}

.body p {
    color: blue;
    margin-bottom: 30px;
}

.body a {
    display: block;
    text-align: center;
    background: blue;
    color: white;
    width:100px;
    padding: 10px;
    margin-bottom: 50px;
}

.body a:hover {
    background: black;
}

.footer {
    background-color: lightpink;
    text-align: center;
    margin: auto;
    padding: 10px;
}
結果

星期日, 11月 23, 2025

[CSS] Flex 基礎練習

課程練習,練習完發現,其實也只有用上 justify-content: space-around 而已,其他就基礎排版囉 

index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>CSS - Flex 基礎練習</title>
</head>

<body>
    <div class="wrap">
        <div class="content">
            <div class="item">
                <img src="https://82534363_4f518a102e_n.jpg" alt="高雄愛河">
                <h2>高雄愛河</h2>
                <p>高雄愛河是穿梭都市的生命之水,兩岸綠樹成蔭,微風徐徐。夜間尤其迷人,河畔燈光倒映水面,波光粼粼,如同一條璀璨的銀帶。遊客可漫步親水步道,或搭乘愛之船,感受迎面而來的浪漫氣息。週末更有市集和街頭藝人,為這片靜謐的河景增添熱鬧與活力,是高雄最具代表性的休憩與觀光勝地。</p>
            </div>
            <div class="item">
                <img src="https://1776884718_0ed0417709_n.jpg" alt="溫哥華市中心">
                <h2>溫哥華市中心</h2>
                <p>溫哥華市中心坐擁壯麗的海灣景色,現代化的玻璃帷幕高樓直插雲霄,與湛藍的英吉利灣和布拉德內灣形成鮮明對比。街道充滿活力,從時尚的羅布森街到歷史悠久的蓋士鎮,處處可見繁忙的購物人潮、多元的餐飲文化。市區緊鄰史丹利公園,讓這座都會在繁華之餘,仍保有親近大自然的綠色魅力。</p>
            </div>
            <div class="item">
                <img src="https://125408140_c9204a223e_n.jpg" alt="台北大直橋">
                <h2>台北大直橋</h2>
                <p>台北大直橋是橫跨基隆河的一座醒目地標,以其獨特的白色拱形鋼纜設計而聞名,流線型的橋身優雅地劃過水面,極具現代感。夜幕降臨後,橋體會亮起多變的燈光秀,與周圍的內湖科技園區和美麗華摩天輪夜景相互輝映,是攝影愛好者的熱點。它不僅是重要的交通樞紐,更是一座融合工程與藝術的城市景觀橋。</p>
            </div>
        </div>
    </div>
</body>
</html>
style.css
.wrap{
    width: 1024px;
    margin: 0 auto;
}

.content{
    display: flex;
    justify-content: space-around;;
}

.item{
    width: 300px;
    padding: 10px;
    border: lightgray 1px solid;
}

.item img{
    width: 100%;
}

.item h2{
    text-align: center;
    font-size: 25px;
}
結果

星期四, 11月 13, 2025

[GAS] HTMLService - setSandboxMode

請 AI 產生需求 Code 時發現沒有使用過的語法 setSandboxMode
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('網頁抬頭')
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
從官方文件發現
SandboxMode 雖然有 NATIVE、EMULATED、IFRAME 三種模式,但 NATIVE、EMULATED 已經在 20151013 淘汰,目前一律以 IFRAME 為主囉,根本就沒有需要透過 setSandboxMode 再次設定的必要

星期一, 11月 10, 2025

[GAS] LineBot 取得圖檔並儲存至 Goolge Drive

Line 有提供 content API 可以取得使用者傳送的圖檔 (Images)、影片 (videos)、音檔 (audio) 和檔案 (files),該筆記會以傳送 jpeg 圖檔 (Images) 為主

content API
https://api-data.line.me/v2/bot/message/{messageId}/content

Response

status code 返回 200 且 content 是 blob,該 blob 會自動被刪除,官方並不保證會留存多久


Error response

呼叫後的 status code 有 404 和 410
  • 404:不存在的 messageID
  • 410:使用者並沒有傳送訊息 (message)
// 404 Not Found json
{
  "message": "not found"
}

// 410 Gone json
{
  "message": "The content is gone"
}

Google Apps Script .gs Code
function doPost(e) {
  try {
    let contents = JSON.parse(e.postData.contents);
    let event = contents.events[0];

    if (event.type === 'message' && 
        event.message &&
        event.message.type === 'image') {

      let messageId = event.message.id;

      let imageBlob = downloadLineImage(messageId);

      if (imageBlob) {
        let folderName = 'LineBot_Images';
        saveBlobToDrive(imageBlob, folderName);
      }
    }

  } catch (e) {
    WriteLog([new Date(), "錯誤訊息:", e.toString()]);
  }
}

function downloadLineImage(messageId) {

  let lineChannelAccessToken = getProperties("LineChannelAccessToken");

  let url = `https://api-data.line.me/v2/bot/message/${messageId}/content`;

  let options = {
    'headers': {
      'Authorization': 'Bearer ' + lineChannelAccessToken,
    },
    'method': 'get',
    'muteHttpExceptions': true,
  };

  try {
    let response = UrlFetchApp.fetch(url, options);
    return response.getBlob();
  } catch (e) {
    WriteLog([new Date(), "錯誤訊息:", "下載 Line 圖片錯誤:" + e.toString()]);
    return null;
  }
}

function saveBlobToDrive(blob, folderName) {

  try {
    // 尋找或建立目標資料夾
    let folder = DriveApp.getFoldersByName(folderName);
    if (folder.hasNext()) {
      folder = folder.next();
    } else {
      folder = DriveApp.createFolder(folderName);
    }

    // 設定檔案名稱 (例如: LineImage_20251028_100000.jpg)
    let timestamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyyMMdd_HHmmss');

    // 範例:blob.getContentType() 回傳 image/jpeg
    let fileName = `LineImage_${timestamp}.${blob.getContentType().split('/').pop()}`;

    // 儲存檔案
    folder.createFile(blob.setName(fileName));
    WriteLog([new Date(), `檔案已儲存到 Drive:${fileName}`]);

  } catch (e) {
    WriteLog([new Date(), "儲存到 Google Drive 錯誤:" + e.toString()]);
  }
}

測試結果