動畫管理員的核心是 managerMaker 函數,負責建立物件並初始化必要的屬性和方法。它包含了管理動畫請求的基本邏輯和資料結構。以下是管理員的基本結構:
const managerMaker = function() {
this.subject = [];
this.request = {};
this.globalKey = "dev";
this.lastRequestName = [];
// 其他方法...
}
這些屬性用於追蹤當前的動畫請求、目標元素及其狀態,為後續的動畫控制奠定基礎。
在動畫管理員中,採用FP的原則,設計幾個主要方法負責核心功能:
註冊時,除了封裝動畫以外,還要檢查名稱是否有效:
this.registerAnimationCallback = (name, callback) => {
this.createAnimation(name, callback);
this.nameValidation(name);
}
在資料格式方面,我們將 name 作為鍵值存入請求物件中,每筆資料包含儲存方法和狀態管理:
this.createAnimation = (name, callback) => {
const animate = () => {
callback();
this.request[name].ID = requestAnimationFrame(animate);
};
this.request[name] = {
method: animate,
isPause: false,
};
}
由於動畫管理員通過觀測對象,來篩選需要呼叫的動畫,在註冊時貼心的檢查該動畫,是否包含在觀測對象列表,適時提出警告:
this.nameValidation = (name) => {
const isValid = Object.keys(this.subject).some(ID => name.includes(ID));
if(!isValid) console.warn("naming issue: " + name + " should include one of following letters: " + this.subject);
}
註銷動畫時,先取消動畫調用,再進行清空、刪除:
this.unregisterAnimationCallback = (name) => {
cancelAnimationFrame(this.request[name].ID);
this.request[name].method = null;
delete this.request[name];
}
最基本的方法如下,就可以根據元素的可見性來播放和暫停動畫:
const names = this.getRequestById(id);
// 請求動畫
names.forEach(name => {
this.request[name].ID = requestAnimationFrame(this.request[name].method);
})
// 取消動畫
names.forEach(name => {
cancelAnimationFrame(this.request[name].ID);
})
把請求列表中的鍵值取出,就能藉由 id 篩選出請求對象:
this.getRequestById = (id) => {
const req = Object.keys(this.request).filter(key => key.includes(id) || key.includes(this.globalKey));
return req;
}
但是,這裡的設計考慮到我們 Playground 元件,包含了數個 canvas 區塊,都是對計算有一定負擔的動畫,為求效能,我們希望每個 canvas 都是獨立的,只允許畫面中的單一 canvas 執行動畫。因此切換頁面時的動作分別如下:
這個做法可以避免在 transition 切換頁面時,因為原本的畫面未離開,而造成效能負擔。
this.updateRequestAnimation = (id) => {
// 1.取消舊的動畫
this.lastRequestName.forEach(name => {
if(!this.request[name]) return;
cancelAnimationFrame(this.request[name].ID);
})
// 2.開始新的動畫
const names = this.getRequestById(id);
if(names === null) return;
this.lastRequestName = names;
names.forEach(name => {
if(typeof this.request[name] === "undefined") return console.warn("invalid request");
if(typeof this.request[name].method !== "function") return console.warn("invalid requestMethod");
if(this.request[name].isPause) return;
this.request[name].ID = requestAnimationFrame(this.request[name].method);
})
}
這裡就是呼叫動畫的邏輯了,只要新的 Canvas 對象進來,就更新動畫。離開則不觸發。
this.io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if(entry.intersectionRatio === 0) return;
this.updateRequestAnimation(entry.target.id);
});
});
如果希望元素獨立管理,在畫面中就執行動畫,畫面外就取消,則可以進行拓展:
if(entry.intersectionRatio === 0){
this.cancelRequestAnimation(id);
return;
}
this.startRequestAnimation(id);
邏輯相同,利用 id 取得請求清單。當作小練習吧:該如何實現這兩個函式?和前面的 updateRequestAnimation 有何不同?
最後,新增和移除觀察對象:
this.addSubjectElement = (element, id = element.id) => {
if (!id) return console.warn("Element must have an ID");
this.subject[id] = element;
this.io.unobserve(element) // 避免多次觀察同一元素
this.io.observe(element);
}
this.removeSubjectID = (id) => {
if (!this.subject[id]) return console.warn("Element ID not found");
const element = this.subject[id];
this.io.unobserve(element);
delete this.subject[id];
}
在 JavaScript 中沒有私有的概念,所以我們就在函式名稱加一個 public 前綴,表示這些開放給外部使用。
在這裡我們可以拓展管理員的功能,提供外部控制方法,例如:
this.publicPauseAnimation = (name) => {
if(!this.request[name]) return;
cancelAnimationFrame(this.request[name].ID);
this.request[name].isPause = true;
}
this.publicResumeAnimation = (name) => {
if(!this.request[name]) return;
this.request[name].isPause = false;
cancelAnimationFrame(this.request[name].ID);
this.request[name].ID = requestAnimationFrame(this.request[name].method);
}
this.publicListAllAnimations = () => {
return Object.keys(this.request);
};
this.publicListLastAnimations = () => {
return this.lastRequestName;
};
在使用動畫管理員時,建議遵循以下原則:
managerMaker 是一個功能強大的動畫管理工具,旨在提高動畫請求的管理效率。通過有效的請求更新、觀察者模式及簡潔的 API,開發者可以更專注於動畫邏輯,而不必擔心底層的管理細節。這個管理員適用於需要高效動畫管理的各種 Web 應用,是提升用戶體驗的理想選擇。