# 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}]"}
    350 views