Google 簡報: 取得正在作用中的簡報 getActivePresentation()

 


想像你正在編輯一份投影片,點一下自訂選單,「插入封面」「刷新樣板」唰唰兩下就完成——背後的關鍵就是 getActivePresentation()。

它像把門鑰匙,直接打開「眼前那份」簡報,省去管理 ID 的麻煩。不過,這把鑰匙只在「前台」好使:有 UI、有焦點、有人按鈕的時候。丟到背景排程或遠端觸發,十之八九撲空。

這篇文章會把情境分清楚:互動用 Active、批次用 ID;還有權限、安全、選單觸發的最佳實務、以及踩坑紀錄。目標很單純——讓你的 Slides 自動化少寫一行錯一半,該快的地方快,該穩的地方穩。希望本篇文章能幫助到需要的您。


目錄

{tocify} $title={目錄} 


先說結論

SlidesApp.getActivePresentation() 會回傳目前在瀏覽器中處於作用中的簡報(active presentation)的 Presentation 物件;如果當前沒有可用的「作用中簡報」,就會回傳 null 或在某些觸發情境下直接拋錯。

它只適合:從 Google 簡報檔案裡的「附加元件按鈕、選單、巨集、Apps Script 編輯器(綁定同一份檔)」觸發的互動流程;不適合定時觸發器(time-driven)、獨立腳本在背景跑、Webhook、或沒有開著簡報的情況。

若你的流程不保證有作用中簡報,請改用 SlidesApp.openById(id) 或 SlidesApp.openByUrl(url) 以檔案 ID/URL 開啟;這是最保險的寫法。

權限方面,操作簡報通常需要 https://www.googleapis.com/auth/presentations 或較窄的 presentations.currentonly scope;Apps Script 會自動偵測,但發佈附加元件時建議明確縮限。


getActivePresentation() 是什麼?為什麼大家會用它?

在 Apps Script 的 Slides 服務裡,SlidesApp.getActivePresentation() 是拿到「目前被使用者在前端開啟、且具有焦點」的那份 Google 簡報的捷徑。拿到這個 Presentation 物件後,你就能:

        讀取標題、擁有者、ID(getId())、版面配置、投影片清單。

        編輯內容:插入文字方塊、圖片、形狀、註解、連結、或複製其他簡報的投影片。

它的好處是「少傳一個參數」:不用自己管理簡報 ID、也不用硬寫死路徑。只要確定腳本的執行環境真有「作用中簡報」,就能秒取物件、直接改。


什麼情境該用?什麼情境別用?

適合:

        1.    簡報內的自訂選單/巨集

在 Slides 檔開著時,點自訂選單的功能,腳本便能抓到作用中簡報並立即處理。

        2.    編輯器附加元件(Slides Add-on) 的前端互動

使用者在簡報中操作附加元件 UI,呼叫後端 Apps Script 時,多半會期望對「眼前這一份」檔案動手。


不適合(高風險):

1.    定時觸發器(time-driven)或 onFormSubmit 這類非 UI 觸發

        沒有任何「使用者前端」與「作用中簡報」概念,getActivePresentation() 可能是 null 或直接拋錯。請改用 openById()。

2.    獨立腳本(standalone)在 Apps Script 主控台執行

        若當下不是從某份簡報的「綁定腳本」開啟,通常沒有 active 檔案上下文。

3.    App 外掛、Webhook、Apps Script API 遠端喚起

        這些多半在 server-to-server 或背景流程裡執行,沒有作用中的文件。

4.    跨帳號或多帳號登入混用情境

        使用者同時在多個 Google 帳號登入時,Active 檔可能被錯誤解析,甚至拋錯;務必以檔案 ID 明確開啟。


前置作業:建立安全、可維護的專案

1.    準備一份測試簡報,並在其「擴充功能 → Apps Script」建立綁定腳本(container-bound)。

2.    在 appsscript.json 縮限權限(若你要發佈附加元件)

       一般讀寫請用 presentations,若只需讀取目前文件可考慮 presentations.currentonly。

3.    避免在 Simple Trigger 做重手術

        Slides 僅支援 onOpen(e) 的簡單觸發,且很多操作在 simple trigger 會受限;若要做大量修改,改由使用者點擊選單來觸發。

4.    必要時改用 openById()/openByUrl()

把目標簡報 ID 存設定檔或試算表,避免「找不到 active presentation」的脆弱依賴。


一步步操作示範

範例 A:抓基本資訊(標題、ID、投影片數)

function logActivePresentationInfo() {
  const p = SlidesApp.getActivePresentation(); // 需有作用中簡報
  if (!p) {
    throw new Error('找不到作用中簡報:請從簡報檔內的綁定腳本執行,或改用 openById()。');
  }
  const title = p.getName();
  const id = p.getId();        // 取得簡報 ID,亦可用於 openById() 打開同一份檔
  const slideCount = p.getSlides().length;

  Logger.log({ title, id, slideCount });
}


重點:getId() 能拿到當前簡報 ID,方便後續記錄或日後背景批次用 openById() 操作。


範例 B:在目前頁面插入方塊並套超連結

function insertCalloutOnCurrentPage() {
  const p = SlidesApp.getActivePresentation();
  if (!p) throw new Error('沒有作用中簡報。');

  // 取得「目前選取的頁面」
  const selection = p.getSelection();
  const page = selection ? selection.getCurrentPage() : null; // 可能為 null
  const slide = page && page.asSlide ? page.asSlide() : p.getSlides()[0];

  const shape = slide.insertShape(SlidesApp.ShapeType.RECTANGLE, 60, 60, 420, 80);
  const textRange = shape.getText();
  textRange.setText('文件自動化成功  立即開啟說明');
  textRange.getTextStyle().setBold(true);

  // 附上外部教學或專案文件的連結
  const link = textRange.getLink();
  if (!link) textRange.setLinkUrl('https://your-docs.example.com/');
}


小知識:你也能在 Slides 內操作連結物件與連結到其他投影片的「非 URL 連結類型」。


範例 C:把另一份範本簡報的第一張投影片「連結式」插入到目前簡報

function appendLinkedSlideFromTemplate(templateId) {
  const current = SlidesApp.getActivePresentation();
  if (!current) throw new Error('沒有作用中簡報。');

  const source = SlidesApp.openById(templateId); // 明確以 ID 開啟
  const srcSlide = source.getSlides()[0];

  // 以「連結」模式插入,後續可刷新來源更新
  const linked = current.appendSlide(
    srcSlide,
    SlidesApp.SlideLinkingMode.LINKED
  );

  // 你也可稍後用 linked.refresh() 更新,或 linked.unlink() 解除連結
  return linked;
}


連結式投影片可保持與來源同步,但刷新時會覆蓋本地變更;務必告知使用者。


避免踩雷:十大常見錯誤與解法

1.    TypeError: Cannot read properties of null(或 p 為 null)

成因:沒有作用中簡報、在背景觸發跑。

解法:改用 openById();或要求使用者從簡報內的選單觸發。

2.    在 onOpen(e) 做大量修改導致不穩

成因:Slides 的 simple trigger 能力有限;某些操作(包含 getActivePresentation())在觸發初期易失敗。

解法:在 onOpen 只建立選單;實際操作放到使用者點選的函式裡。

3.    多帳號登入導致 Active 解析錯亂或授權錯

成因:同時登入多個 Google 帳號,前端與後端會話不一致。

解法:建議改 openById(),並請使用者以單一帳號測試。

4.    授權被放大或不足

成因:Apps Script 自動偵測 scopes,但註解的程式碼也可能被掃描;附加元件上架需要最小權限。

解法:在 appsscript.json 明確設定需要的 Scopes(例如 presentations.currentonly),並審視每一次變更。

5.    以為 getActivePresentation() 到處都能用

成因:混淆文件情境(Docs/Sheets/Slides 各有 Active 概念,但觸發環境不同)。

解法:只在有 UI 的互動流程用;其他一律 openById()。

6.    把「樣板投影片」硬貼上導致後續維護困難

成因:單純複製而非用「連結式」插入,來源更新不會跟上。

解法:若要長期維護,改用 SlideLinkingMode.LINKED,必要時再 unlink()。

7.    在觸發器中讀取「目前選到的頁面」卻拿到 null

成因:沒有使用者前端選取狀態。

解法:流程設計上改為由使用者在前端點擊後再執行,或自動定位預設頁面。

8.    把 getActiveSpreadsheet() 的行為套用到 Slides

成因:跨產品遷移經驗,忽略不同容器差異。

解法:檢查對應產品可用的觸發、授權、與 Active 規則;若在附加元件或所有者轉移後出現 null,先以 ID 開啟驗證。

9.    在 onOpen 裡嘗試 openById() 期望切換到別份簡報

說明:onOpen 只是目前檔案的生命週期事件,並不是用來「跳檔」。

建議:保持 onOpen 輕量,將跨檔操作放在使用者指令流程。

10.    未建立「ID 管理機制」

成因:過度依賴 Active,導致批次/背景流程不可移植。

解法:把簡報 ID 集中存放於試算表或屬性服務(PropertiesService),並建立讀寫 API;所有批次流程一律以 ID 開啟。


實務設計建議(可直接套入專案)


1.    雙路徑策略(互動 vs. 批次)

        互動路徑:Active 模式 → getActivePresentation(),體驗快、少參數。

        批次路徑:明確檔案模式 → openById(),穩定可重跑。

2.    選單只做導航,不做重工

        onOpen(e) 只建立 UI;真正修改內容的函式由使用者點下去再跑。

3.    權限最小化

        發佈附加元件前,檢查 manifest scopes,盡量使用 presentations.currentonly;只有在需要跨檔存取時才升級到完整 presentations。

4.    以「連結式」維護範本

        用 SlideLinkingMode.LINKED 管理模板同步;教育使用者「刷新會覆蓋本地變更」。


除錯指南(快速路徑)

先判斷執行環境:是從簡報裡的選單按鈕觸發嗎?若不是,直接改用 openById()。

檢查多帳號:請測試者暫時只登入一個 Google 帳號。

看授權:到專案設定開啟 manifest 檢視,核對 scopes 是否過大或過小;重新授權一次。

簡化 onOpen:把重邏輯移到使用者點選的命令函式。

記錄 ID:用 getId() 把當前檔案 ID 打到 Logger,複製下來改跑 openById() 比對行為。


問題集

Q1:為什麼我在 Apps Script 編輯器直接跑 getActivePresentation() 會是 null?

A:你多半不是從簡報的「綁定專案」執行,或當下沒有作用中的 Slides UI。建議改從簡報裡的「擴充功能 → Apps Script」執行,或直接使用 openById()。

Q2:可不可以在定時觸發器裡跑 getActivePresentation()?

A:不建議。定時觸發器沒有前端環境,拿不到 Active。請以 openById() 開檔。

Q3:onOpen(e) 能不能直接幫我把模板插入?

A:技術上可行但不穩定,且會影響開檔速度。建議 onOpen 只做 UI;插入動作由使用者點選後再執行。

Q4:我要發佈附加元件,要用哪些 Scopes?

A:視你的功能而定。若只操作當前檔案,優先考慮 presentations.currentonly;若需跨檔讀寫再用 presentations。在 manifest 明確列出,並確保說明文件解釋用途。

Q5:如何取得目前選到的投影片?

A:SlidesApp.getActivePresentation().getSelection().getCurrentPage() 可取到當前頁,再用 asSlide() 轉為 Slide 操作;在沒有前端選取的情境下可能為 null。


實作模板

1.    建立選單 + 安全操作骨架

function onOpen(e) {
  SlidesApp.getUi()
    .createMenu('自動化工具')
    .addItem('插入標準版頭', 'action_insertHeader')
    .addItem('同步模板首頁', 'action_syncCover')
    .addToUi();
}

function getCurrentPresentationOrFail() {
  const p = SlidesApp.getActivePresentation();
  if (!p) throw new Error('找不到作用中簡報。請從簡報內使用選單執行,或改用 openById()。');
  return p;
}

function action_insertHeader() {
  const p = getCurrentPresentationOrFail();
  const slide = p.getSlides()[0];
  const shape = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, 40, 40, 520, 48);
  shape.getText().setText('公司名|報告主題|YYYY-MM-DD').getTextStyle().setBold(true);
}

function action_syncCover() {
  const TEMPLATE_ID = '你的範本簡報ID';
  const p = getCurrentPresentationOrFail();
  const src = SlidesApp.openById(TEMPLATE_ID);
  const srcSlide = src.getSlides()[0];
  const linked = p.appendSlide(srcSlide, SlidesApp.SlideLinkingMode.LINKED);
  // 可另外提供 UI 告知:連結頁面刷新時會覆蓋本地改動
}


這個骨架把「抓 Active」包在小函式裡,集中處理錯誤訊息;也示範了互動式(點選單)才執行重操作的做法。


2.    批次流程版本(沒有 Active 的情境)

function batchUpdateCoverById(presentationId) {
  const p = SlidesApp.openById(presentationId); // 關鍵:不要依賴 Active
  const slide = p.getSlides()[0];
  slide.replaceAllText('{{DATE}}', Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd'));
  // 其他批次作業...
}


批次流程用 openById() 是王道,也利於部署到多份檔。


張貼留言 (0)
較新的 較舊