bounceKeyframe_simple.jsx
(function () {
app.beginUndoGroup("Apply Bounce Animation");
var comp = app.project.activeItem;
if (!(comp instanceof CompItem)) {
alert("コンポジションを開いてください。");
return;
}
var selectedLayers = comp.selectedLayers;
if (selectedLayers.length === 0) {
alert("レイヤーを選択してください。");
return;
}
for (var i = 0; i < selectedLayers.length; i++) {
var layer = selectedLayers[i];
var position = layer.property("Position");
if (!position.canSetExpression) {
alert("このレイヤーの位置にキーフレームを設定できません。");
continue;
}
var startTime = comp.time;
var duration = 1.2; // バウンスの持続時間
var bounces = 4; // バウンス回数
var amplitude = 200; // 初期振幅
var damping = 2.5; // 減衰率
var initialPos = position.value;
var endPos = [initialPos[0], initialPos[1] + amplitude, initialPos[2] || 0];
position.setValueAtTime(startTime, initialPos);
position.setValueAtTime(startTime + 0.1, endPos);
for (var j = 1; j <= bounces; j++) {
var t = startTime + j * (duration / bounces);
var bounceHeight = amplitude / Math.pow(damping, j);
var bouncePos = [initialPos[0], initialPos[1] + (j % 2 === 0 ? bounceHeight : -bounceHeight), initialPos[2] || 0];
position.setValueAtTime(t, bouncePos);
}
// 最終的な位置
position.setValueAtTime(startTime + duration, initialPos);
}
app.endUndoGroup();
})();
bounceKeyframe.jsx
(function () {
function createUI(thisObj) {
var win = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Bounce Animation", undefined, {resizeable: true});
win.orientation = "column";
win.alignChildren = "left";
// バウンスの設定
win.add("statictext", undefined, "バウンスの設定:");
var bounceGroup = win.add("group");
bounceGroup.orientation = "row";
bounceGroup.add("statictext", undefined, "回数:");
var bounceInput = bounceGroup.add("edittext", undefined, "4");
bounceInput.characters = 3;
var amplitudeGroup = win.add("group");
amplitudeGroup.orientation = "row";
amplitudeGroup.add("statictext", undefined, "振幅:");
var amplitudeInput = amplitudeGroup.add("edittext", undefined, "200");
amplitudeInput.characters = 5;
var dampingGroup = win.add("group");
dampingGroup.orientation = "row";
dampingGroup.add("statictext", undefined, "減衰率:");
var dampingInput = dampingGroup.add("edittext", undefined, "2.5");
dampingInput.characters = 3;
var durationGroup = win.add("group");
durationGroup.orientation = "row";
durationGroup.add("statictext", undefined, "持続時間:");
var durationInput = durationGroup.add("edittext", undefined, "1.2");
durationInput.characters = 4;
var frequencyGroup = win.add("group");
frequencyGroup.orientation = "row";
frequencyGroup.add("statictext", undefined, "波の周期:");
var frequencyInput = frequencyGroup.add("edittext", undefined, "1.0");
frequencyInput.characters = 3;
var easingGroup = win.add("group");
easingGroup.orientation = "row";
easingGroup.add("statictext", undefined, "イージング:");
var easingDropdown = easingGroup.add("dropdownlist", undefined, ["リニア", "イーズイン", "イーズアウト", "イーズイン・アウト"]);
easingDropdown.selection = 0;
// バウンス方向
var directionGroup = win.add("group");
directionGroup.orientation = "row";
directionGroup.add("statictext", undefined, "方向:");
var directionDropdown = directionGroup.add("dropdownlist", undefined, ["下 → 上", "上 → 下"]);
directionDropdown.selection = 0;
// 軸の選択
var axisGroup = win.add("group");
axisGroup.orientation = "row";
axisGroup.add("statictext", undefined, "適用軸:");
var xCheckbox = axisGroup.add("checkbox", undefined, "X軸");
var yCheckbox = axisGroup.add("checkbox", undefined, "Y軸");
yCheckbox.value = true; // デフォルトでY軸に適用
var rotationCheckbox = axisGroup.add("checkbox", undefined, "回転");
// 適用ボタン
var applyButton = win.add("button", undefined, "バウンスを適用");
applyButton.onClick = function () {
var bounces = parseInt(bounceInput.text, 10);
var amplitude = parseFloat(amplitudeInput.text);
var damping = parseFloat(dampingInput.text);
var duration = parseFloat(durationInput.text);
var frequency = parseFloat(frequencyInput.text);
var direction = directionDropdown.selection.index; // 0: 下 → 上, 1: 上 → 下
var easingType = easingDropdown.selection.index; // 0: リニア, 1: イーズイン, 2: イーズアウト, 3: イーズイン・アウト
var applyX = xCheckbox.value;
var applyY = yCheckbox.value;
var applyRotation = rotationCheckbox.value;
if (isNaN(bounces) || isNaN(amplitude) || isNaN(damping) || isNaN(duration) || isNaN(frequency)) {
alert("すべての値を正しく入力してください!");
return;
}
applyBounceAnimation(bounces, amplitude, damping, duration, frequency, direction, easingType, applyX, applyY, applyRotation);
};
if (win instanceof Window) {
win.center();
win.show();
}
}
function applyBounceAnimation(bounces, amplitude, damping, duration, frequency, direction, easingType, applyX, applyY, applyRotation) {
app.beginUndoGroup("Apply Bounce Animation");
var comp = app.project.activeItem;
if (!(comp instanceof CompItem)) {
alert("コンポジションを開いてください。");
return;
}
var selectedLayers = comp.selectedLayers;
if (selectedLayers.length === 0) {
alert("レイヤーを選択してください。");
return;
}
for (var i = 0; i < selectedLayers.length; i++) {
var layer = selectedLayers[i];
var position = layer.property("Position");
var rotation = layer.property("Rotation");
var startTime = comp.time;
var initialPos = position.value;
var initialRotation = rotation.value;
var endPos = [
applyX ? initialPos[0] + (direction === 0 ? amplitude : -amplitude) : initialPos[0],
applyY ? initialPos[1] + (direction === 0 ? -amplitude : amplitude) : initialPos[1],
initialPos[2] || 0
];
var endRotation = applyRotation ? initialRotation + 20 : initialRotation;
function getEasedTime(t, type) {
switch (type) {
case 1: return Math.pow(t, 3); // イーズイン
case 2: return 1 - Math.pow(1 - t, 3); // イーズアウト
case 3: return t < 0.5 ? 4 * Math.pow(t, 3) : 1 - Math.pow(-2 * t + 2, 3) / 2; // イーズイン・アウト
default: return t; // リニア
}
}
if (applyX || applyY) {
position.setValueAtTime(startTime, initialPos);
position.setValueAtTime(startTime + 0.1, endPos);
for (var j = 1; j <= bounces; j++) {
var t = startTime + j * (duration / bounces);
var easedT = getEasedTime(j / bounces, easingType) * frequency;
var bounceHeight = amplitude / Math.pow(damping, j);
var bouncePos = [
applyX ? initialPos[0] + (j % 2 === 0 ? bounceHeight : -bounceHeight) : initialPos[0],
applyY ? initialPos[1] + (j % 2 === 0 ? -bounceHeight : bounceHeight) : initialPos[1],
initialPos[2] || 0
];
position.setValueAtTime(t * easedT, bouncePos);
}
position.setValueAtTime(startTime + duration, initialPos);
}
}
app.endUndoGroup();
}
createUI(this);
})();
(function () {
function createUI(thisObj) {
var win = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Bounce Animation", undefined, {resizeable: true});
win.orientation = "column";
win.alignChildren = "left";
// バウンスの設定
win.add("statictext", undefined, "バウンスの設定:");
var bounceGroup = win.add("group");
bounceGroup.orientation = "row";
bounceGroup.add("statictext", undefined, "回数:");
var bounceInput = bounceGroup.add("edittext", undefined, "4");
bounceInput.characters = 3;
var amplitudeGroup = win.add("group");
amplitudeGroup.orientation = "row";
amplitudeGroup.add("statictext", undefined, "振幅:");
var amplitudeInput = amplitudeGroup.add("edittext", undefined, "200");
amplitudeInput.characters = 5;
var dampingGroup = win.add("group");
dampingGroup.orientation = "row";
dampingGroup.add("statictext", undefined, "減衰率:");
var dampingInput = dampingGroup.add("edittext", undefined, "2.5");
dampingInput.characters = 3;
var durationGroup = win.add("group");
durationGroup.orientation = "row";
durationGroup.add("statictext", undefined, "持続時間:");
var durationInput = durationGroup.add("edittext", undefined, "1.2");
durationInput.characters = 4;
// バウンス方向
var directionGroup = win.add("group");
directionGroup.orientation = "row";
directionGroup.add("statictext", undefined, "方向:");
var directionDropdown = directionGroup.add("dropdownlist", undefined, ["下 → 上", "上 → 下"]);
directionDropdown.selection = 0;
// 適用軸の選択
var axisGroup = win.add("group");
axisGroup.orientation = "row";
axisGroup.add("statictext", undefined, "適用軸:");
var xCheckbox = axisGroup.add("checkbox", undefined, "X軸");
var yCheckbox = axisGroup.add("checkbox", undefined, "Y軸");
yCheckbox.value = true; // デフォルトでY軸に適用
var rotationCheckbox = axisGroup.add("checkbox", undefined, "回転");
// 非線形バウンスオプション
var nonLinearGroup = win.add("group");
nonLinearGroup.orientation = "row";
var nonLinearCheckbox = nonLinearGroup.add("checkbox", undefined, "後半のキーフレームを多くする");
nonLinearCheckbox.value = true;
// 適用ボタン
var applyButton = win.add("button", undefined, "バウンスを適用");
applyButton.onClick = function () {
var bounces = parseInt(bounceInput.text, 10);
var amplitude = parseFloat(amplitudeInput.text);
var damping = parseFloat(dampingInput.text);
var duration = parseFloat(durationInput.text);
var direction = directionDropdown.selection.index; // 0: 下 → 上, 1: 上 → 下
var applyX = xCheckbox.value;
var applyY = yCheckbox.value;
var applyRotation = rotationCheckbox.value;
var nonLinear = nonLinearCheckbox.value;
if (isNaN(bounces) || isNaN(amplitude) || isNaN(damping) || isNaN(duration)) {
alert("すべての値を正しく入力してください!");
return;
}
applyBounceAnimation(bounces, amplitude, damping, duration, direction, applyX, applyY, applyRotation, nonLinear);
};
if (win instanceof Window) {
win.center();
win.show();
}
}
function applyBounceAnimation(bounces, amplitude, damping, duration, direction, applyX, applyY, applyRotation, nonLinear) {
app.beginUndoGroup("Apply Bounce Animation");
var comp = app.project.activeItem;
if (!(comp instanceof CompItem)) {
alert("コンポジションを開いてください。");
return;
}
var selectedLayers = comp.selectedLayers;
if (selectedLayers.length === 0) {
alert("レイヤーを選択してください。");
return;
}
for (var i = 0; i < selectedLayers.length; i++) {
var layer = selectedLayers[i];
var position = layer.property("Position");
var rotation = layer.property("Rotation");
var startTime = comp.time;
var initialPos = position.value;
var initialRotation = rotation.value;
function getNonLinearTime(index) {
if (nonLinear) {
return startTime + duration * (1 - Math.exp(-index / (bounces / 2)));
} else {
return startTime + index * (duration / bounces);
}
}
var endPos = [
applyX ? initialPos[0] + (direction === 0 ? amplitude : -amplitude) : initialPos[0],
applyY ? initialPos[1] + (direction === 0 ? -amplitude : amplitude) : initialPos[1],
initialPos[2] || 0
];
var endRotation = applyRotation ? initialRotation + 20 : initialRotation; // 20度回転
if (applyX || applyY) {
position.setValueAtTime(startTime, initialPos);
position.setValueAtTime(getNonLinearTime(0.1), endPos);
for (var j = 1; j <= bounces; j++) {
var t = getNonLinearTime(j);
var bounceHeight = amplitude / Math.pow(damping, j);
var bouncePos = [
applyX ? initialPos[0] + (j % 2 === 0 ? bounceHeight : -bounceHeight) : initialPos[0],
applyY ? initialPos[1] + (j % 2 === 0 ? -bounceHeight : bounceHeight) : initialPos[1],
initialPos[2] || 0
];
position.setValueAtTime(t, bouncePos);
}
position.setValueAtTime(getNonLinearTime(bounces), initialPos);
}
if (applyRotation) {
rotation.setValueAtTime(startTime, initialRotation);
rotation.setValueAtTime(getNonLinearTime(0.1), endRotation);
for (var j = 1; j <= bounces; j++) {
var t = getNonLinearTime(j);
var bounceRotation = initialRotation + (j % 2 === 0 ? 10 : -10) / Math.pow(damping, j);
rotation.setValueAtTime(t, bounceRotation);
}
rotation.setValueAtTime(getNonLinearTime(bounces), initialRotation);
}
}
app.endUndoGroup();
}
createUI(this);
})();