use-outside-comp
Use a color control from an outside comp.
thisComp.layer('Outside Comp').effect('Color Control')(time);Use a color control from an outside comp or layer control.
An ever–expanding library of expressions and scripts for After Effects.
expressions and scripts.
Use a color control from an outside comp.
thisComp.layer('Outside Comp').effect('Color Control')(time);Use a color control from an outside comp or layer control.
References reusable color controls on controller layers inside the current composition.
thisComp.layer('TOP_NAV_CTRL').effect('Background Color')('Color');
thisComp.layer('NULL_LAYER_NAME').effect('Effect_Name')('Color');
Replace the example layer and effect names with the controls used in your comp.
Adds slow organic movement without making the layer feel unstable.
wiggle(0.4, 24);Best for background elements, floating details, or soft camera movement.
Creates stepped random motion instead of constantly interpolated motion.
posterizeTime(8);
wiggle(3, 18);Good for stop-motion, jitter, or intentionally graphic movement.
Rotates continuously at a predictable speed without keyframes.
time * 45;Change 45 to control degrees per second.
Formats the current system time in several 12-hour and 24-hour display styles.
// Render the current time, formatted → 12:00 PM
d = new Date();
currentTime = d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
currentTime;
// Render the current time, formatted → 12:00:00 PM
d = new Date();
currentTime = d.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
});
currentTime;
// Render the current time, formatted → 12:00
d = new Date();
currentTime = d.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: false,
});
currentTime;
// Render the current time, formatted → X:00
d = new Date();
hours = d.getHours();
minutes = d.getMinutes();
if (hours > 12) {
hours -= 12;
} else if (hours === 0) {
hours = 12;
}
currentTime = hours + ':' + minutes;
currentTime;
Apply to Source Text and keep the formatting block you want to use.
Replaces the interpolation between keyframes with an exponential ease for 1D, 2D, or 3D values.
/**
* @author @chvndler
*
* @description easing function
* @version 1.0.0
*/
function keyflow_inOut(t, b, c, d) {
var CORRECTION = 0.000976563;
var v;
if ((t /= d / 2) < 1) {
v = Math.pow(2, 10 * (t - 1)) - CORRECTION;
} else {
v = -Math.pow(2, -10 * (t - 1)) + 2 + CORRECTION;
}
return b + (v / 2) * c;
}
function keyEase() {
var n = 0;
if (numKeys > 0) {
n = nearestKey(time).index;
if (key(n).time > time) {
n--;
}
}
try {
var key1 = key(n);
var key2 = key(n + 1);
} catch (e) {
return null;
}
// determine how many dimensions the keyframes need
var dim = 1; // It's gotta have at least ONE dimension
try {
key(1)[1];
dim = 2;
key(1)[2];
dim = 3;
} catch (e) {}
t = time - key1.time;
d = key2.time - key1.time;
sX = key1[0];
eX = key2[0] - key1[0];
if (dim >= 2) {
sY = key1[1];
eY = key2[1] - key1[1];
if (dim >= 3) {
sZ = key1[2];
eZ = key2[2] - key1[2];
}
}
if (time < key1.time || time > key2.time) {
return value;
} else {
val1 = keyflow_inOut(t, sX, eX, d);
switch (dim) {
case 1:
return val1;
break;
case 2:
val2 = keyflow_inOut(t, sY, eY, d);
return [val1, val2];
break;
case 3:
val2 = keyflow_inOut(t, sY, eY, d);
val3 = keyflow_inOut(t, sZ, eZ, d);
return [val1, val2, val3];
break;
default:
return null;
}
}
}
keyEase() || value;
Apply to a property with at least two keyframes.
Applies a guarded exponential ease only within the active keyframe segment.
/**
* @author @chvndler
*
* @description easing function
* @version 1.0.0
*/
// Drop on any keyframed property (1D/2D/3D). Only affects time BETWEEN keys.
function easeInOutExpo(t, b, c, d) {
var CORRECTION = 1 / 1024; // avoids tiny edge artifacts
var v;
if (d === 0) return b;
t /= d / 2; // normalize to 0..2
if (t < 1) {
v = Math.pow(2, 10 * (t - 1)) - CORRECTION; // ease-in
} else {
v = -Math.pow(2, -10 * (t - 1)) + 2 + CORRECTION; // ease-out
}
return b + v * 0.5 * c; // v/2 maps to 0..1
}
function getDimFromKey(prop) {
// Returns 1, 2, or 3 based on the shape of key(1)
try {
prop.key(1)[2];
return 3;
} catch (e3) {
try {
prop.key(1)[1];
return 2;
} catch (e2) {
return 1;
}
}
}
function currentKeySegment(prop, t) {
// Returns {k1, k2} for the segment containing time t, or null if none.
if (prop.numKeys < 2) return null;
var n = prop.nearestKey(t).index;
if (prop.key(n).time > t) n--;
if (n < 1 || n >= prop.numKeys) return null;
return { k1: prop.key(n), k2: prop.key(n + 1) };
}
function easeBetweenKeysExpo(prop, t) {
var seg = currentKeySegment(prop, t);
if (!seg) return null;
var k1 = seg.k1;
var k2 = seg.k2;
if (t < k1.time || t > k2.time) return null;
var lt = t - k1.time; // local time into the segment
var d = k2.time - k1.time; // segment duration
var dim = getDimFromKey(prop);
// Build result per dimension
if (dim === 1) {
var s = k1[0];
var c = k2[0] - k1[0];
return easeInOutExpo(lt, s, c, d);
}
if (dim === 2) {
var sx = k1[0],
cx = k2[0] - k1[0];
var sy = k1[1],
cy = k2[1] - k1[1];
return [easeInOutExpo(lt, sx, cx, d), easeInOutExpo(lt, sy, cy, d)];
}
// dim === 3
var sx3 = k1[0],
cx3 = k2[0] - k1[0];
var sy3 = k1[1],
cy3 = k2[1] - k1[1];
var sz3 = k1[2],
cz3 = k2[2] - k1[2];
return [easeInOutExpo(lt, sx3, cx3, d), easeInOutExpo(lt, sy3, cy3, d), easeInOutExpo(lt, sz3, cz3, d)];
}
// Apply
easeBetweenKeysExpo(thisProperty, time) || value;
Works with 1D, 2D, and 3D keyframed properties.
Rounds a slider value and displays it as text.
Math.round(effect('Slider Control')('Slider'));Add a Slider Control effect to the text layer first.
Uses a layer marker comment as editable source text.
marker.numKeys > 0 ? marker.key(1).comment : value;Good for quickly changing text without opening the Source Text property.
Formats the current system date as either a numeric date or a written weekday and date.
// Render the current date, formatted → DD/MM/YYYY
d = new Date();
currentDate = d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear();
currentDate;
// Render the current date, formatted → Thursday, December 21
d = new Date();
currentDate = d.toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
});
currentDate;
Apply to Source Text and keep the formatting block you want to use.
Positions consecutive text layers with equal visual gaps based on their rendered widths.
// X Position — equal gaps by using the *right edge* of previous text
// Needs "Spacing" Slider on layer "MENU_CTRL"
// Assumes the row starts at layer 1 and text layers are consecutive
var gap = thisComp.layer('MENU_CTRL').effect('Spacing')('Slider');
var first = thisComp.layer(1);
function w(L) {
var r = L.sourceRectAtTime(time, false);
return r.width * (L.transform.scale[0] / 100);
}
function centerOffset(L) {
var r = L.sourceRectAtTime(time, false);
return (-r.left + r.width / 2) * (L.transform.scale[0] / 100);
}
function rightOffset(L) {
// distance from anchor to right edge
var r = L.sourceRectAtTime(time, false);
return (-r.left + r.width) * (L.transform.scale[0] / 100);
}
// first layer edges
var firstLeft = first.transform.xPosition - centerOffset(first);
var firstRight = first.transform.xPosition + rightOffset(first);
if (index == first.index) {
firstLeft + centerOffset(thisLayer);
} else {
var totalPrev = 0;
for (var i = first.index + 1; i < index; i++) {
totalPrev += w(thisComp.layer(i));
}
var left = firstRight + totalPrev + (index - first.index) * gap;
left + centerOffset(thisLayer);
}
Apply to X Position and add a Spacing slider to a layer named MENU_CTRL.
Pads a rounded slider value with leading zeroes until it is three digits long.
// Forces a number to have 3 leading zeroes
var s = effect('Slider Control')('Slider').value.toFixed(0);
(s.length < 3 ? (s.length < 2 ? '00' : '0') : '') + s;
Apply to Source Text and add a Slider Control to the layer.
Sizes a shape layer rectangle to match another layer with padding.
textLayer = thisComp.layer('Text');
padding = [48, 28];
box = textLayer.sourceRectAtTime(time, false);
[box.width + padding[0], box.height + padding[1]];Apply to a Rectangle Path Size property.
Apply to any property.
Assumes freq, decay, and amp sliders on a CONTROLS layer.
const ctrl = thisComp.layer("CONTROLS");
const freq = ctrl.effect("freq")("Slider"); // e.g., 3.0
const amp = ctrl.effect("amp")("Slider"); // e.g., 5.0
const decay = ctrl.effect("decay")("Slider"); // e.g., 7.0
let n = 0;
if (numKeys > 0) {
n = nearestKey(time).index;
if (key(n).time > time) n--;
}
if (n === 0) {
value;
} else {
let t = time - key(n).time;
let v = velocityAtTime(key(n).time - thisComp.frameDuration / 10);
value + v * amp * Math.sin(freq * t * 2 * Math.PI) / Math.exp(decay * t);
}Reusable spring overshoot expression.
Converts the frame-to-frame change of a slider into a looping vertical position offset.
temp = effect('Number')('Slider');
a = temp.value;
b = temp.valueAtTime(time - thisComp.frameDuration);
[0, (b - a) * (thisComp.height * 0.5 - ((time * 10000) % thisComp.height))];
Apply to a 2D property and add a Slider Control named Number.
Adds a decaying overshoot after each keyframe using the property’s outgoing velocity.
amp = 0.08;
freq = 2.0;
decay = 5.0;
n = 0;
time_max = 4;
if (numKeys > 0) {
n = nearestKey(time).index;
if (key(n).time > time) {
n--;
}
}
if (n == 0) {
t = 0;
} else {
t = time - key(n).time;
}
if (n > 0 && t < time_max) {
v = velocityAtTime(key(n).time - thisComp.frameDuration / 10);
value + (v * amp * Math.sin(freq * t * 2 * Math.PI)) / Math.exp(decay * t);
} else {
value;
}
Apply to a keyframed property and tune amp, freq, decay, and time_max in the expression.
Creates a velocity-based decay wobble driven by sliders on a dedicated controller layer.
CTRL = thisComp.layer('VELOCITY_CTRL');
// VELOCITY-DRIVEN DECAY WOBBLE
// Apply to any keyframed property (Position, Scale, Rotation)
//
// How it works:
// After each keyframe, the property inherits its exit velocity,
// then oscillates and settles using a decaying sine wave.
//
// ------------------------------------------------------------
// CONTROLS (VELOCITY_CTRL layer)
// ------------------------------------------------------------
//
// Amp (Amplitude)
// • Scales how far the wobble travels
// • Multiplies the keyframe’s exit velocity
// • Higher = bigger overshoot
// • Typical range: 0.03 – 0.25
//
// Freq (Frequency, Hz)
// • Oscillations per second
// • Higher = faster, tighter vibration
// • Lower = slower, floatier motion
// • Typical range: 1 – 10
//
// Decay (Damping strength)
// • Controls how fast the wobble fades out
// • Higher = settles faster
// • Lower = longer, looser motion
// • Typical range: 2 – 15
//
// Time Max (Seconds)
// • Hard cutoff for the wobble after a keyframe
// • Once exceeded, motion returns to pure keyframes
// • Useful for art direction + performance
// • Typical range: 0.5 – 4
// ------------------------------------------------------------
// slider fetch with defaults
function ctrl(name, def) {
try {
CTRL.effect(name)('Slider').value;
} catch (e) {
def;
}
}
// default values (used if sliders don’t exist)
amp = ctrl('amp', 0.08);
freq = ctrl('freq', 2.0);
decay = ctrl('decay', 5.0);
time_max = ctrl('time_max', 2.0);
// ------------------------------------------------------------
// most recent keyframe
// ------------------------------------------------------------
n = 0;
if (numKeys > 0) {
n = nearestKey(time).index;
if (key(n).time > time) n--;
}
// time since last keyframe
t = n == 0 ? 0 : time - key(n).time;
// ------------------------------------------------------------
// apply decaying oscillation
// ------------------------------------------------------------
if (n > 0 && t < time_max) {
// sample velocity just before the keyframe
v = velocityAtTime(key(n).time - thisComp.frameDuration / 10);
// decaying sine-wave wobble
value + (v * amp * Math.sin(freq * t * 2 * Math.PI)) / Math.exp(decay * t);
} else {
value;
}
Create a VELOCITY_CTRL layer with amp, freq, decay, and time_max Slider Controls.
Centers the anchor point based on the layer bounds.
box = sourceRectAtTime(time, false);
[box.left + box.width / 2, box.top + box.height / 2];Apply to Anchor Point on text or shape layers.
Keeps a slider-driven value inside a predictable range.
slider = effect('Slider Control')('Slider');
clamp(slider, 0, 100);Useful when exposing controls through Essential Graphics.