# Animation.js
路徑位置: `/ads-adcode/src/util/animation.js`
有三個 `function` 可以使用
1. loadTweenMax$
2. processCanvasAnimation$ **目前只有用到**
3. processAnimation$
---
## 流程圖
```flow
st=>start: start
op0=>operation: processCanvasAnimation$()
e=>end: end
cond=>condition: set TweenMax Version 3?
op=>operation: loadTweenMax$()
op2=>operation: loadGSAP$()
op3=>operation: handleCanvas$()
op4=>operation: createCanvas()
op5=>operation: appendToTarget()
op6=>operation: handleCanvasAnimation$()
op7=>operation: _do___________Animation()
st->op0->cond
cond(yes)->op2->op3->op4->op5->op6->op7->e
cond(no)->op->op3->op4->op5
```
---
## 主要思考方式
把原本應該顯示的圖片隱藏起來
已經**載入完成**的圖片透過 `processCanvasAnimation$()` 傳入後將圖片切割為 n*m 的小 `canvas`
切割完成後的小 canvas append 到原本圖片的位置
對每個小 canvas 綁上 GSAP 的動畫達到效果
動畫結束後把 canvas 砍掉
原本圖片做的設定回復
---
## processCanvasAnimation$()
參數有
1. imgElement: 選到要做切割的圖片標籤
2. container: 要 append 的位置
3. animationType: 要使用的動畫名稱
4. animationEndCallback: 動畫完成後做的一些還原設定
5. options: 一些圖片的資訊, n*m 設定, 動畫重複次數..
---
```javascript=
function processCanvasAnimation$(imgElement, container, animationType, animationEndCallback, options) {
canvasCol = options.col || canvasCol;
canvasRow = options.row || canvasRow;
animationRepeatTimes = options.animationRepeatTimes || animationRepeatTimes;
yoyoMode = options.yoyoMode || yoyoMode;
if(options.gsapVersion === '3') {
loadGSAP$().then(function() {
return handleCanvas$(imgElement, container, animationType, animationEndCallback, options);
});
} else if(options.gsapVersion === '2' || typeof options.gsapVersion === 'undefined') {
loadTweenMax$().then(function() {
return handleCanvas$(imgElement, container, animationType, animationEndCallback, options);
});
}
}
```
---
## handleCanvas$()
計算圖片比例長寬,要切割的數量,每個 canvas 的長寬
---
```javascript=
function handleCanvas$(imgElement, container, animationType, animationEndCallback, options) {
return new Promise(function(resolve, reject) {
// do split image to canvas
var rectangle = canvasCol * canvasRow;
var rectangleWidth = options.width / canvasCol;
var rectangleHeight = options.height / canvasRow;
var naturalWidth = options.naturalWidth;
var naturalHeight = options.naturalHeight;
var imageRation = Math.round(naturalWidth / options.width * 100) / 100;
var heightRation = Math.round(naturalHeight / options.height * 100) / 100;
var canvasArr = createCanvas(canvasCol, canvasRow, rectangleWidth, rectangleHeight, rectangle, imgElement, imageRation, heightRation);
var targetCanvas = appendToTarget(imgElement, canvasArr, container, rectangleWidth * canvasCol, rectangleHeight * canvasRow);
resolve();
handleCanvasAnimation$(targetCanvas, imgElement, container, animationType, animationEndCallback, options);
});
}
```
---
## createCanvas()
**精華所在**
利用迴圈切割出 canvas 並且每個 canvas 的 style 設定,返回一個陣列
---
```javascript=
function createCanvas(col, row, rectangleWidth, rectangleHeight, rectangle, imgEle, imageRation, heightRation) {
// canvas 陣列
var arr = [];
var startPoint = true;
// 幾個四邊形方格
var planeCounter = 0;
// 高度階層
var heightLevel = 1;
for(var i = 1; i <= rectangle; i++) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 每個四邊形的寬高
var xPointer = rectangleWidth;
var yPointer = rectangleHeight;
canvas.style.position = 'relative';
// canvas.style.top = '0';
// canvas.style.left = planeCounter * rectangleWidth + 'px';
canvas.id = 'adneon-canvas-' + i;
canvas.classList.add('adneon-canvas-cube');
canvas.width = rectangleWidth;
canvas.height = rectangleHeight;
// 切第一個一定是從圖座標 (0, 0) 開始
if(startPoint) {
ctx.drawImage(
imgEle,
0, 0,
xPointer * imageRation, yPointer * heightRation,
0, 0,
rectangleWidth, rectangleHeight * heightLevel);
arr.push(canvas);
planeCounter += 1;
startPoint = false;
} else {
// 畫出除了第一層第一個四邊形之外所有同層的四邊形
if(planeCounter % col !== 0) {
if(heightLevel > 1) {
ctx.drawImage(
imgEle,
xPointer * imageRation * planeCounter, rectangleHeight * (heightLevel - 1) * heightRation,
xPointer * imageRation * (planeCounter + 1), yPointer * heightRation * heightLevel,
0, 0,
rectangleWidth * (planeCounter + 1), rectangleHeight * heightLevel);
planeCounter += 1;
arr.push(canvas);
} else {
ctx.drawImage(
imgEle,
xPointer * imageRation * planeCounter, 0,
xPointer * imageRation * (planeCounter + 1), yPointer * heightRation,
0, 0,
rectangleWidth * (planeCounter + 1), rectangleHeight);
planeCounter += 1;
arr.push(canvas);
}
} else {
heightLevel++;
if(planeCounter === rectangle) {
return [];
// 畫出第二層開始每一層的第一個四邊形
} else {
planeCounter = 0;
ctx.drawImage(
imgEle,
planeCounter * xPointer * imageRation, yPointer * (heightLevel - 1) * heightRation,
rectangleWidth * imageRation, rectangleHeight * heightLevel * heightRation,
0, 0,
rectangleWidth, rectangleHeight * heightLevel);
planeCounter += 1;
arr.push(canvas);
}
}
}
}
return arr;
}
```
---
## appendToTarget()
貼回該長的位置
這裡可能需要加上原本圖片就有的樣式設定
---
```javascript=
function appendToTarget(imgElement, canvasArr, target, containerWidth, containerHeight) {
var canvasContainer = document.createElement('div');
cloneAttributes(canvasContainer, imgElement);
canvasContainer.id = 'adneon-canvas-container';
canvasContainer.setAttribute('position', 'relative');
canvasContainer.style.width = containerWidth + 'px';
canvasContainer.style.display = 'flex';
canvasContainer.style.flexWrap = 'wrap';
canvasContainer.style.opacity = 1;
for(var i = 0; i < canvasArr.length; i++) {
canvasContainer.appendChild(canvasArr[i]);
}
target.insertAdjacentElement('beforeEnd', canvasContainer);
return canvasContainer;
}
function cloneAttributes(targetEle, sourceNode) {
var attr;
var attributes = Array.prototype.slice.call(sourceNode.attributes);
while(attr = attributes.pop()) {
targetEle.setAttribute(attr.nodeName, attr.nodeValue);
}
}
```
---
## handleCanvasAnimation$() ==**<font color="#f00">增加動畫時需要修改</font>**==
根據 `animationType` 的不同來呼叫不同的 `_do_________Animation()`
```javascript=
function handleCanvasAnimation$(targetCanvas, imgElement, container, animationType, animationEndCallback, options) {
// animation end will emit animation-end event
// options.creativeInfo._emit('animation-end');
switch (animationType) {
case 'horizontal-move':
_doHorizontalMoveAnimation(targetCanvas, imgElement, container, animationEndCallback, options);
break;
case 'shutters':
_doShuttersAnimation(targetCanvas, imgElement, container, animationEndCallback, options);
break;
case 'canvas-split':
// do nothing
break;
default:
break;
}
// gsap animation debug tool
// if use this feature will fire onComplete event two times
if(options.devTool && animationType !== '') {
GSDevTools.create();
var tool = document.querySelector('.gs-dev-tools');
tool.style.bottom = options.height + 'px';
}
}
```
---
## _do___________Animation() ==**<font color="#f00">增加動畫時需要修改</font>**==
實作 TweenMax 動畫的地方
如果不知道怎麼做可以參考之前分享的 [TweenMax 動畫入門](https://hackmd.io/@zackgibson/B13JPdczu)
```javascript=
function _doShuttersAnimation(targetCanvas, imgElement, container, animationEndCallback, options) {
var animationDuration = 2;
var perPlaneDelay = 0;
imgElement.style.display = 'none';
var canvasCubes = targetCanvas.querySelectorAll('.adneon-canvas-cube');
canvasCubes = Array.apply(null, canvasCubes);
// TweenMax 3.x.x
if(options.gsapVersion === '3') {
ADNEON_GSAP_3_NAMESPACE.gsap.from(canvasCubes, {
duration: animationDuration,
x: options.width,
rotationY: 90,
opacity: 0,
repeat: animationRepeatTimes,
yoyo: yoyoMode,
stagger: {
each: perPlaneDelay
},
onComplete: function() {
if(!options.devTool) {
imgElement.style.opacity = '1';
imgElement.style.display = 'block';
targetCanvas.parentNode.removeChild(targetCanvas);
}
animationEndCallback();
}
});
// TweenMax 2.1.3
} else {
PPSTUDIO_TWEENMAX_NAMESPACE.TweenMax.staggerFrom(canvasCubes, animationDuration, {
x: options.width,
rotationY: 90,
opacity: 0,
repeat: animationRepeatTimes,
yoyo: yoyoMode
}, perPlaneDelay, function() {
imgElement.style.opacity = '1';
imgElement.style.display = 'block';
targetCanvas.parentNode.removeChild(targetCanvas);
animationEndCallback();
});
}
}
```
---
## 以行動置底旋轉摩天輪三圖蓋版當範例
---
```javascript=
case 'shutters':
var currentFront = adContainer.querySelector('.current.fg');
var currentBack = adContainer.querySelector('.mobile-bottom-carousel-image.current');
currentBack.style.opacity = 0;
currentFront.style.opacity = 0;
var logo = adContainer.querySelector('#tenmax-pps-logo');
logo && (logo.style.opacity = 0);
var anchorContainer = currentBack.parentElement;
var canvasAnimationOptions = {
creativeInfo: creativeInfo,
height: currentBack.clientHeight,
width: currentBack.clientWidth,
naturalHeight: currentBack.naturalHeight,
naturalWidth: currentBack.naturalWidth
// gsap devTool
// ,devTool: false
// gsap version
};
canvasAnimationOptions.col = 4;
canvasAnimationOptions.row = 1;
helpers.animation.processCanvasAnimation$(
currentBack,
anchorContainer,
animationType,
animationEndCallback,
canvasAnimationOptions);
break;
function animationEndCallback() {
console.log('%c' + animationType.toUpperCase() + ' Animation End',
'border: 1px solid red; margin-left: -20px; background: black; color: white; font-weight: 900; font-size: 20px;');
}
```
{"metaMigratedAt":"2023-06-15T21:46:52.394Z","metaMigratedFrom":"Content","title":"Animation.js","breaks":true,"contributors":"[{\"id\":\"bda81afc-f13e-4d82-9677-5359a2a615c9\",\"add\":23573,\"del\":13201}]"}