很多人寫 Apps Script 都從 getRange('A1:B10') 開始,但寫到後來你會發現,真正好用的工具其實是跟著使用者操作走的。
getActiveRange() 讓腳本尊重當下選取,不用硬把範圍寫死,做起來更貼近現場流程。本文先用最短的步驟帶你跑通第一次:開啟編輯器、授權、執行;接著把常見需求拆給你看——單值/批次寫入、字型/底色/邊框、數字與日期格式、資料驗證、註解與超連結。
會特別示範多選範圍的處理方式,還有跟 getCurrentCell()、getActiveSheet() 的搭配。文末我把踩雷清單整理成檢查表:為什麼會拿到 null、為什麼 setValues() 一直報錯、遇到合併儲存格怎麼辦、觸發器該改寫哪段。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
什麼是「Active Range」?getActiveRange() 的定位
Active Range(使用者目前選取的區域):在試算表 UI 中,你滑鼠拖拉反白的那一塊,就是 Active Range。
getActiveRange():在 GAS 端取得「當下使用者選取」的範圍(Range 物件)。
語法有兩種等價寫法:
const range = SpreadsheetApp.getActiveRange();
// 或
const range = SpreadsheetApp.getActive().getActiveRange();
回傳可能是 null(例如:腳本不是由使用者在該試算表互動觸發;或使用時間驅動觸發器執行,沒有 UI 也就沒有選取)。
什麼時候該用?
需要對「使用者剛選到的儲存格或區域」做事:填值、上色、標註、驗證、建立超連結、快速格式化…
製作側邊欄/選單工具,讓使用者「先選再按鈕」。
最快上手:從 0 到能跑(操作步驟)
1. 開啟試算表 → 工具列點「擴充功能」→「Apps Script」,進到 GAS 編輯器。
2. 新增檔案 Code.gs,貼上下面入門範例。
3. 回到試算表,選一塊範圍,再回 GAS 按「執行」。
4. 首次會跳權限授權,依流程允許。
5. 回到試算表確認結果。
入門範例:在使用者選取區域填入時間戳與作者
function stampSelection() {
const range = SpreadsheetApp.getActiveRange();
if (!range) {
SpreadsheetApp.getUi().alert('沒有選取範圍。請先反白一塊儲存格或區域再執行。');
return;
}
const user = Session.getActiveUser().getEmail() || 'unknown';
const ts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss');
const values = range.getValues().map(row => row.map(() => `${ts} by ${user}`));
range.setValues(values);
}
基礎用法大全
讀取選取區的值與尺寸
function readActiveRangeInfo() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
Logger.log(`Sheet: ${r.getSheet().getName()}`);
Logger.log(`Rows: ${r.getNumRows()}, Cols: ${r.getNumColumns()}`);
Logger.log(r.getValues());
}
寫入資料(單值 / 二維陣列)
function writeHello() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
if (r.getNumRows() === 1 && r.getNumColumns() === 1) {
r.setValue('Hello');
} else {
const data = Array.from({length: r.getNumRows()}, () =>
Array.from({length: r.getNumColumns()}, () => 'Hello')
);
r.setValues(data); // 尺寸必須完全相符
}
}
基礎格式(字型、顏色、邊框、對齊)
function quickFormat() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
r.setFontFamily('Noto Sans')
.setFontSize(12)
.setFontWeight('bold')
.setHorizontalAlignment('center')
.setVerticalAlignment('middle')
.setBackground('#FFF9C4') // 淡黃(提升可讀性)
.setFontColor('#333333');
r.setBorder(true, true, true, true, true, true);
}
數字與日期格式
function numberAndDateFormat() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
r.setNumberFormat('[Blue]#,##0;[Red]-#,##0;0'); // 正藍負紅
r.offset(0, 1).setNumberFormat('yyyy-mm-dd'); // 右側欄設定日期
}
驗證規則(清單、數字區間、必填)
function addValidations() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const ruleList = SpreadsheetApp.newDataValidation()
.requireValueInList(['高', '中', '低'], true)
.setAllowInvalid(false)
.setHelpText('請從下拉選單選擇:高/中/低')
.build();
const ruleNumber = SpreadsheetApp.newDataValidation()
.requireNumberBetween(0, 100)
.setAllowInvalid(false)
.setHelpText('0~100 的數字')
.build();
r.setDataValidation(ruleList);
r.offset(0, 1, r.getNumRows(), 1).setDataValidation(ruleNumber);
}
超連結與註解
function addLinksAndNotes() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
// 為第一欄加上超連結
const firstCol = r.offset(0, 0, r.getNumRows(), 1);
const values = firstCol.getValues().map(row => {
const text = row[0];
return [`=HYPERLINK("https://www.google.com/search?q=${encodeURIComponent(text)}","查詢「${text}」")`];
});
firstCol.setValues(values);
// 為整個選取區加上註解
r.setNote('自動產生的連結,請確認內容是否正確');
}
進階:多選範圍 getActiveRangeList() 與批次處理
使用者按住 Ctrl/⌘ 多選不連續區塊時,getActiveRange() 只能抓到「第一塊」。要一次處理多塊,請用 getActiveRangeList():
function batchPaintMultiSelection() {
const rangeList = SpreadsheetApp.getActive().getActiveRangeList();
if (!rangeList) {
SpreadsheetApp.getUi().alert('沒有多重選取。請按住 Ctrl/⌘ 選取多塊區域再執行。');
return;
}
rangeList.getRanges().forEach(r => {
r.setBackground('#E3F2FD').setFontWeight('bold');
});
}
小技巧:
需要同時設定邊框、格式、驗證,建議先把要套的設定封裝成函式,對 rangeList.getRanges() 迴圈呼叫,並搭配 SpreadsheetApp.flush() 在大量操作後一次刷新(見效能章節)。
真實場景 10 例
批次清洗空白
function trimSpaces() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const data = r.getValues().map(row => row.map(v => (typeof v === 'string') ? v.trim() : v));
r.setValues(data);
}
一鍵標記狀態(完成/待辦)
function toggleDone() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const data = r.getValues().map(row => row.map(v => v === '完成' ? '待辦' : '完成'));
r.setValues(data);
r.setBackground('#C8E6C9');
}
快速插入遞增編號
function fillSerial() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const rows = r.getNumRows(), cols = r.getNumColumns();
let n = 1;
const serials = Array.from({length: rows}, () => Array.from({length: cols}, () => n++));
r.setValues(serials);
}
把選取資料轉大寫/小寫/Title Case
function toUpperCaseSel() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const data = r.getValues().map(row => row.map(v => (typeof v === 'string') ? v.toUpperCase() : v));
r.setValues(data);
}
批次設為千分位、百分比
function thousandAndPercent() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
r.setNumberFormat('#,##0');
r.offset(0, 1, r.getNumRows(), 1).setNumberFormat('0.00%');
}
依值條件上色(自訂規則)
function colorByValue() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const values = r.getValues();
for (let i = 0; i < r.getNumRows(); i++) {
for (let j = 0; j < r.getNumColumns(); j++) {
const cell = r.getCell(i+1, j+1);
const v = values[i][j];
cell.setBackground(v >= 0 ? '#E8F5E9' : '#FFEBEE');
}
}
}
將選取區搬運到新工作表
function moveSelectionToNewSheet() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const ss = r.getSheet().getParent();
const sh = ss.insertSheet('剪下的資料 ' + new Date().toLocaleString());
sh.getRange(1,1, r.getNumRows(), r.getNumColumns()).setValues(r.getValues());
r.clearContent();
}
建立資料驗證下拉 + 預設值
function dropdownWithDefault() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList(['新進','在職','離職'], true)
.setAllowInvalid(false)
.build();
r.setDataValidation(rule).setValue('在職');
}
把選取區轉為圖表資料區(加上標題列)
function markAsChartData() {
const r = SpreadsheetApp.getActiveRange();
if (!r) return;
r.offset(-1, 0, 1, r.getNumColumns()).setFontWeight('bold'); // 假設上方一列為標題
// 後續可用 Charts 服務建立圖表(略)
}
多選範圍批次貼上格式(RangeList)
function pasteFormatToMulti() {
const list = SpreadsheetApp.getActive().getActiveRangeList();
if (!list) return;
const [first, ...rest] = list.getRanges();
rest.forEach(r => first.copyFormatToRange(r.getSheet(), r.getColumn(), r.getLastColumn(), r.getRow(), r.getLastRow()));
}
錯誤訊息與常見雷點
1. range 為 null
情境:用時間驅動或 Web App 呼叫,沒有 UI → 沒有選取。
解法:
改在使用者有 UI 的情境執行(自訂選單 / 按鈕)。
或改設計:用固定範圍(sheet.getRange('A1:B10'))或從參數接收。
2. setValues() 尺寸不符
訊息:The number of rows in the data does not match the number of rows in the range.
解法:二維陣列的列數、欄數 必須 跟 Range 尺寸完全一致。善用:
const rows = r.getNumRows(), cols = r.getNumColumns();
const data = Array.from({length: rows}, () => Array.from({length: cols}, () => 'x'));
r.setValues(data);
3. 合併儲存格導致寫入失敗
setValues() 寫入到有合併的區塊很容易報錯。
解法:
if (r.isPartOfMerge()) {
r.breakApart(); // 視需求拆開,或重新選取不含合併的區域
}
4. 多選範圍卻用 getActiveRange()
只會抓到第一塊,其他被忽略。
解法:使用 getActiveRangeList(),對 getRanges() 逐一處理。
5. 權限不足 / 第一次授權
第一次執行可能跳「需要授權」。
解法:按流程授權。若是部署給他人使用,請用「編輯器附加元件」或在同域共享,並說明授權步驟。
6. 觸發器中使用 getActiveRange()
簡單觸發器(如 onEdit(e))有 e.range 可用;時間驅動觸發器沒有 UI,getActiveRange() 通常 null。
解法:在 onEdit(e) 用 e.range,在時間排程改用固定 Range 邏輯。
7. 跨試算表誤用
SpreadsheetApp.getActiveRange() 只會取 目前活躍的試算表。
解法:跨檔案請改 SpreadsheetApp.openById(id).getSheetByName(name).getRange(...),不要依賴 Active。
問題集
Q1:為何我執行時 getActiveRange() 是 null?
A:多半因為沒有 UI(時間觸發、API 呼叫)或沒有真正選到範圍。請改 UI 互動場合,或改以固定座標。
Q2:我要處理多塊不連續範圍?
A:使用 getActiveRangeList() → getRanges(),逐一操作。
Q3:setValues() 一直爆錯?
A:檢查二維陣列尺寸是否完全一致;合併儲存格是否已拆開。
Q4:要不要用 getCurrentCell()?
A:只針對單一焦點格時更合適;需要整塊就用 getActiveRange()。
Q5:我想把選到的資料搬去另一個檔案?
A:不要用 Active 去開別檔;直接 openById() 目標檔,再 getSheetByName() + getRange() 寫入。
