CanvasとGSAP
画像を複数行に分割して
複数のCanvas要素で表示するdrawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
sx (元画像の切り抜き始点X)
sy (元画像の切り抜き始点Y)
sWidth (元画像の切り抜きサイズ:横幅)
sHeight (元画像の切り抜きサイズ:高さ)
dx (Canvasの描画開始位置X)
dy (Canvasの描画開始位置Y)
dWidth (Canvasの描画サイズ:横幅)
dHeight (Canvasの描画サイズ:高さ)
<div id="parent" style="width:300px; height:200px"></div>
<style>
.canvas{
display: block;
}
</style>
<script>
const parent = document.getElementById('parent')
const imgW = 640 //元画像
const imgH = 426
const canvasW = 300 //表示幅
const canvasH = imgH/imgW * canvasW
const low = 10 //行数
function loadCanvas(id) {
let canvas = document.createElement('canvas');
canvas.id = id;
canvas.width = canvasW;
canvas.height = canvasH/low;
canvas.className = "block";
parent.appendChild(canvas)
}
//それぞれのCanvasに画像を表示する
function loadImg(id,posY) {
let canvas = document.getElementById(id);
let c = canvas.getContext('2d');
let imageObj = new Image();
imageObj.onload = function() {
let sourceX = 0;
let sourceY = posY;
let sourceWidth = imgW;
let sourceHeight = imgH/low;
let destWidth = canvasW
let destHeight = canvasH/low
let destX = 0;
let destY = 0;
c.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
}
imageObj.src = '...';
}
for (let i = 0; i < low; i++) {
let curCanvasId="canvas_Column" + i;
loadCanvas(curCanvasId);
loadImg(curCanvasId, i * imgH/low);
}
</script>
それぞれのCanvasをGSAPを使ってアニメーションする
*画像をクリックした時にアニメーション
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
<script>
//省略
const rotation = gsap.to(".canvas", {
duration: 3,
rotationY: 360,
stagger: 0.1
});
rotation.pause();
parent.addEventListener("click", ()=>{
rotation.restart();
})
</script>
画像の上をクリック
staggerについて
stagger: 0.1
各トゥイーンの開始時間の間に 0.1 秒が生じます
負の値は最後の要素が最初に始まるようになります
staggerは詳細オプションの設定ができますstagger:{ ここにオプション}
- amount:合計時間 (秒単位)
- each:各トゥイーンの間に一定時間
- from:特定の要素から始める
*配列の場合はインデックスを表す数値
*”start”・”center”・”edges”・ “random”・ “end” - grid:要素が視覚的にグリッドに表示されている場合
- axis:1つの軸だけに焦点を当てる
- ease
画像は行列で分割
stagger詳細オプションでgridを試してみる
<style>
#parent{
display:flex;
flex-wrap: wrap;
}
</style>
<div id="parent" style="width:300px; height:200px"></div>
<script>
const parent = document.getElementById('parent')
const imgW = 640 //元画像
const imgH = 427
const canvasW = 300 //表示幅
const canvasH = imgH/imgW*canvasW
const low = 5 //行数
const col = 6 //列数
function loadCanvas(id) {
let canvas = document.createElement('canvas');
canvas.id = id;
canvas.width = canvasW/col;
canvas.height = canvasH/low;
canvas.className = "canvas";
parent.appendChild(canvas)
}
//それぞれのCanvasに画像を表示する
function loadImg(id, posY, posX) {
let canvas = document.getElementById(id);
let c = canvas.getContext('2d');
let imageObj = new Image();
imageObj.onload = function() {
let sourceX = posX;
let sourceY = posY;
let sourceWidth = imgW/col
let sourceHeight = imgH/low
let destWidth = canvasW/col
let destHeight = canvasH/low
let destX = 0;
let destY = 0;
c.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
}
imageObj.src = '...';
}
for (let j = 0; j < low; j++) {
for (let i = 0; i < col; i++) {
let id=`row${j}_col${i}`
loadCanvas(id);
loadImg(id, j * imgH/low, i * imgW/col);
}
}
}
//gsap
const tl = gsap.timeline();
const moveGrid = tl.to(".canvas", {
duration: 1, x:300,
stagger: { each: 0.1, grid: "auto", from: "edges"}
})
.to(".canvas", {
duration: 1, x:0,
stagger: { each: 0.1, grid: "auto", from: "start"}
})
.to(".canvas", {
duration: 1, y:-200,
stagger: { each: 0.1, grid: "auto", from: "random"}
})
.to(".canvas", {
duration: 1, y: 0,
stagger: { each: 0.1, grid: "auto", from: "end"}
})
.to(".canvas", {
duration: 1, rotationZ:360,
stagger: { each: 0.1, grid: "auto", from: "center"}
})
moveGrid.pause();
parent.addEventListener("click", ()=> moveGrid.restart())
</script>
画像の上をクリック
SVGとGSAP
SVGオブジェクト(三角形)をgsap.setメソッドで描画
gsap.toメソッドでアニメーション
<div id="parent2" style="width:300px; height:200px; background:black;">
<svg id="svg" height=100% width=100%></svg>
</div>
<script>
class Triangle {
constructor(id, x1, y1, x2, y2, x3, y3, fill){
this.id = id;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
this.fill = fill;
}
draw() {
svg.innerHTML +=
`<polygon id="${this.id}" class="triangle" points="${this.x1} ${this.y1}, ${this.x2} ${this.y2} ,${this.x3} ${this.y3}, ${this.x1} ${this.y1}" fill="${this.fill}"/>`;
}
}
function setTriangles() {
const w = 300
const h = 200
const row = 6
const col = 10
const gap = 5
let stepX = Math.floor(w/col)
let stepY = Math.floor(h/row)
let y1 = gap, y2 =stepY-gap , y3 = stepY-gap
let i = 0;
for (let j = 0; j < row ; j++) {
let x1 = gap, x2 = gap, x3 = stepX-gap;
for (let k = 0; k <= col; k++) {
i++;
new Triangle("triangle"+i, x1, y1, x2, y2, x3, y3, "yellow").draw();
gsap.set("#triangle"+i, {
transformOrigin:"-50% -50%",
});
x1 +=stepX;
x2 +=stepX;
x3 +=stepX;
}
y1 += stepY;
y2 += stepY;
y3 += stepY;
}
}
setTriangles()
const rotation2 = gsap.to(".triangle", {
duration: 2,
rotation: 360,
skewY: 180,
stagger: function(index, target, list) { // インデックスの奇数と偶数で時間をずらす
let gap = 0.5
if(index % 2 == 0){ gap = 0.5}
else { gap = 0.1}
return index/index*gap
}
});
rotation2.pause();
const parent2 = document.getElementById('parent2')
parent2.addEventListener("click", ()=> {
rotation2.restart();
});
</script>
staggerに関数を設定できます
stagger : function ( index , target , list ) {
// カスタム ロジック
return //合計遅延を返す
}
画像の上をクリック
SVGフィルターをの属性を「AttrPlugin」でアニメーション
feTurbulence(ノイズを設定する)
feDisplacementMap(元の要素を変形し効果を適応する)
<div style="width:300px; height="200px;">
<svg id="container" viewBox="10 10 290 183">
<defs>
<filter id="displacementFilter">
<feTurbulence id="feTurbulence" type="turbulence" baseFrequency="0.02" numOctaves="2" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="10" xChannelSelector="B" yChannelSelector="R"></feDisplacementMap>
</filter>
</defs>
<image xlink:href="..." width="300" height="200" style="filter:url(#displacementFilter)"></image>
</svg>
</div>
<script>
const svgFilter = gsap.to("#feTurbulence", 10, {attr:{baseFrequency:0.06}})
const container =document.getElementById('container')
svgFilter.pause();
container.addEventListener("click", ()=> {
svgFilter.restart();
});
</script>
画像の上をクリック
CSS(Filterと3DTransform)
filter:”hue-rotate(90deg)”で色を反転させます
<img id="filterImg" src="..." style="width:300px; height:200px;">
<script>
const timeline2 = gsap.timeline();
const tlFilter = timeline2
.to("#filterImg", 5, {filter:
"hue-rotate(90deg)"})
.to("#filterImg", 1,{filter:
"hue-rotate(0deg)"})
tlFilter.pause();
const filterImg =document.getElementById('filterImg')
filterImg.addEventListener("click", ()=> {
tlFilter.restart();
});
</script>
画像の上をクリック
3DTransformで平面に奥行きをつけて変形させます
<div id="div" style="width:300px;">
<img src="..." style="width:300px; height:200px;">
</div>
<script>
gsap.set("#div", {
transformOrigin: "50% 50% 200%", //x・y・zのoffsetの設定
transformPerspective: 2000, //奥行き
rotationY: 0})
gsap.to("#div", 5, {rotationY: 360});
</script>
画像の上をクリック
設定なし
x・y・zのoffsetの設定あり
transformOrigin: “50% 50% 200%”
- transformOriginは「 “top”・”left”・ “right”・ “bottom”キーワードの設定もできます」
- 回転角度は時計回り (「_cw」)・反時計回り (「_ccw」)・最短方向 (「_short」) が使えます
*例:rotation: “-170_short”, rotationX: “-=30_cw”
EndArrayとSnapPlugin
endArray
イージングを適用して数値の配列を別の数値の配列にトゥイーンします
*長さが異なる配列は両方の配列にあるインデックスのみがアニメーション化されます
<div id="rect1" style="width:20px;height:20px; background:red; margin:20px 0"></div>
<div id="rect2" style="width:20px;height:20px; background:blue; margin:20px 0"></div>
<div id="rect3" style="width:20px;height:20px; background:green; margin:20px 0"></div>
<div id="rect4" style="width:20px;height:20px; background:yellow; margin:20px 0"></div>
<script>
let array = [10, 20, 30, 280];
gsap.to(array, 5, {
endArray: [100, 200, 280, 0],
onUpdate() {
gsap.set("#rect1", {x: array[0]});
gsap.set("#rect2", {x: array[1]});
gsap.set("#rect3", {x: array[2]});
gsap.set("#rect4", {x: array[3]});
},
ease: "power2.out"
});
</script>
SnapPlugin
特定の配列またはインクリメント内の最も近い値に「スナップ」してトゥイーンします
<svg width="300" height="100">
<circle id="cr1" cx="25" cy="50" r="25" fill="orange"/>
</svg>
<script>
gsap.to("#cr1", 5, { x: 250, ease: "none", snap: {x: 50}}); //インクリメント50
</script>