Handboek Annuleren

Syntax differences between expression engines

This document explains the expression language syntax differences between the JavaScript and Legacy ExtendScript expression engines in After Effects 16.0.

Refer to this document to learn how to improve your expressions for the JavaScript expression engine, or when fixing errors that occur when expressions written for previous releases of After Effects fail to evaluate in the JavaScript expression engine.

The expression language in After Effects is based on JavaScript, which is an implementation of ECMAScript. The JavaScript expression engine in After Effects 16.0 is based on ECMAScript 2018. The Legacy ExtendScript expression engine is based on ECMAScript 3 (1999). (Adobe ExtendScript is also the language used for scripting in After Effects and other Adobe applications.)

You can follow the examples provided below, as well as guidance for how to author expressions that work in both the JavaScript and Legacy ExtendScript expression engines.

The core differences between the JavaScript and Legacy ExtendScript expression engines are:

Modern JavaScript syntax: Improvements made to the expression language

Expressions can use JavaScript syntax from ECMAScript 2018

Many additions have been made to the JavaScript language since ECMAScript 3. There are new methods to use with strings, arrays, and objects which are more compact and readable. There are also new ways to declare variables and functions, as well as default parameters, spread operators and more. This document does not cover these changes, as they are general to the JavaScript language. Resources for learning about the many syntax additions can be found at the following links:

Additional recommended, in-depth resources for learning JavaScript:

.value is no longer required when linking to other properties from Source Text

When referencing another property value from a Source Text property, the Legacy ExtendScript engine requires .value to be added at the end of the property. The JavaScript engine shows the value of the property by default unless another attribute like .propertyIndex or .name is explicitly used.

For example:

thisComp.layer("Solid 1").transform.position
// In the Legacy engine, this evaluates to "[object Property]" when applied to a Source Text property.
// In the JavaScript engine, this evaluates to the value of the Position property of layer "Solid 1" when applied to a Source Text property.
thisComp.layer("Solid 1").transform.position // In the Legacy engine, this evaluates to "[object Property]" when applied to a Source Text property. // In the JavaScript engine, this evaluates to the value of the Position property of layer "Solid 1" when applied to a Source Text property.
thisComp.layer("Solid 1").transform.position
 
// In the Legacy engine, this evaluates to "[object Property]" when applied to a Source Text property.
// In the JavaScript engine, this evaluates to the value of the Position property of layer "Solid 1" when applied to a Source Text property.

Freezing property values with posterizeTime(0)

In After Effects 16.0, posterizeTime(0) freezes the property value at time 0 in the composition. This applies to both the JavaScript and Legacy ExtendScript engines.

Opmerking:

This is not backward-compatible and may cause unexpected results in After Effects versions prior to 16.0.

Incompatible Legacy Syntax

Nearly all of the expression syntax from the Legacy ExtendScript expression engine is forward-compatible with the JavaScript expression engine. However, there is some legacy syntax which is not compatible with the JavaScript expression engine. Sometimes this is caused by syntax changes in modern JavaScript. In other cases, obsolete or outdated syntax has been removed. Examples of non-working and working syntax are provided below.

Most of these syntax difference can be corrected via application scripting that re-writes the expressions.

if...else syntax differences

In general, it is recommended to always write if...else statements with line breaks and brackets in accordance with MDN guidelines. The Legacy ExtendScript engine was tolerant of loose syntax in if...else statements, however the JavaScript engine is strict. Incorrect if...else syntax fails to evaluate when using the JavaScript engine.

Ending an expression in an if statement without an else is not allowed

When an expression ends with an if statement without an else statement, the JavaScript engine will fail to evaluate the expression with the error "Undefined value used in expression (could be an out of range array subscript?)". In the Legacy ExtendScript engine, the following expression evaluates to 100 if the time is greater than 1 second, else it evaluates to 50:

var x = 50;
if ( time > 1 ) {
x = 100
}
// The "else" here is implied but not stated.
var x = 50; if ( time > 1 ) { x = 100 } // The "else" here is implied but not stated.
var x = 50;
if ( time > 1 ) {
    x = 100
}
// The "else" here is implied but not stated.

The JavaScript engine needs the else portion of the statement to be explicitly stated if it is the last statement in the expression:

var x = 50;
if ( time > 1 ) {
x = 100;
} else {
x;
}
var x = 50; if ( time > 1 ) { x = 100; } else { x; }
var x = 50;
if ( time > 1 ) {
    x = 100;
} else {
    x;
}

This will evaluate correctly in both the JavaScript engine and the Legacy ExtendScript engine.

if and else cannot be on the same line without brackets

An if...else statement on a single line without brackets evaluates in the Legacy ExtendScript engine, but fails to evaluate in the JavaScript engine with an error such as "Syntax Error: Unexpected token else" or "Undefined value used in expression (could be an out of range array subscript?)". The error varies, depending on the context and property type.

In the Legacy ExtendScript engine, the following expression evaluates to 100 if the time is greater than 1 second, else it evaluates to 50:

if ( time > 1 ) 100 else 50;
if ( time > 1 ) 100 else 50;
if ( time > 1 ) 100 else 50;

The JavaScript engine requires line breaks or brackets to evaluate if...else statements. For simple cases, the ternary operator may be used instead. Any of the following syntax can be used with the JavaScript engine:

// Solution A: adding a line break before "else" will allow both engines to evaluate correctly.
if ( time > 1 ) 100
else 50;
// Solution B: adding correct bracketing will also allow both engines to evaluate correctly.
if ( time > 1 ) { 100 } else { 50 };
// Solution C: Use a ternary operator in place of the if...else statement, which also evaluates correctly in both engines.
time > 1 ? 100 : 50;
// Solution A: adding a line break before "else" will allow both engines to evaluate correctly. if ( time > 1 ) 100 else 50; // Solution B: adding correct bracketing will also allow both engines to evaluate correctly. if ( time > 1 ) { 100 } else { 50 }; // Solution C: Use a ternary operator in place of the if...else statement, which also evaluates correctly in both engines. time > 1 ? 100 : 50;
// Solution A: adding a line break before "else" will allow both engines to evaluate correctly.
 
if ( time > 1 ) 100
else 50;
 
// Solution B: adding correct bracketing will also allow both engines to evaluate correctly.
 
if ( time > 1 ) { 100 } else { 50 };
 
// Solution C: Use a ternary operator in place of the if...else statement, which also evaluates correctly in both engines.
 
time > 1 ? 100 : 50;

All of the above solutions evaluate correctly in both the JavaScript engine and the Legacy ExtendScript engine.

Expressions cannot end in a function declaration

If an expression ends with a function declaration, the JavaScript engine fails to evaluate the expression with the error "Object of type found where a Number, Array, or Property is needed". In the JavaScript engine, the last item evaluated must return a value, rather than declare one.

The following example works in the Legacy engine, but not the JavaScript engine:

timesTen( value ); // The Legacy engine evaluates this line, even though the function is declared below.
function timesTen ( val ) {
return val * 10
}
timesTen( value ); // The Legacy engine evaluates this line, even though the function is declared below. function timesTen ( val ) { return val * 10 }
timesTen( value );  // The Legacy engine evaluates this line, even though the function is declared below.
 
function timesTen ( val ) {
    return val * 10
}

When a function is called as the last line (instead of the declaration), the expression will evaluate correctly in both engines:

function timesTen ( val ) {
return val * 10
}
timesTen( value ); // The JavaScript engine needs the function call to happen below the declaration in order to return the correct value.
function timesTen ( val ) { return val * 10 } timesTen( value ); // The JavaScript engine needs the function call to happen below the declaration in order to return the correct value.
function timesTen ( val ) {
    return val * 10
}
 
timesTen( value );  // The JavaScript engine needs the function call to happen below the declaration in order to return the correct value.

this() shorthand syntax is not allowed; use thisLayer() instead

In the Legacy ExtendScript engine, this was allowed to be used as shorthand for thisLayer. In the JavaScript engine, this refers to the global object and thisLayer must be used instead. Using this in the JavaScript engine will usually result in the error "this is not a function".

In the following Legacy ExtendScript example, this is used to create a compact link to a Text Layer Position property from the Source Text property:

this(5)(2).value;
this(5)(2).value;
this(5)(2).value;

In the JavaScript engine, this must be replaced by thisLayer:

thisLayer(5)(2).value;
thisLayer(5)(2).value;
thisLayer(5)(2).value;

Using thisLayer is compatible with both expression engines.

Source Text property array-index access to characters requires .value

In the Legacy ExtendScript expression engine, the characters of a text property could be accessed with bracket notation like an array:

text.sourceText[0] // Returns the first character of the Source Text property's text value.
text.sourceText[0] // Returns the first character of the Source Text property's text value.
text.sourceText[0]      // Returns the first character of the Source Text property's text value.

In the JavaScript engine, .value must be added in order to access the characters:

text.sourceText.value[0] // Returns the first character of the Source Text property's text value.
text.sourceText.value[0] // Returns the first character of the Source Text property's text value.
text.sourceText.value[0]    // Returns the first character of the Source Text property's text value.

This syntax is compatible with both engines.

Snake case properties and methods are not allowed

The deprecated snake case properties and methods (written with an underscore instead of camelCase) are not supported by the JavaScript engine. The camelCase versions should be used instead as they are compatible with both engines. The following is a list of the deprecated snake case and their corresponding camelCase.

Snake case properties

camelCase properties

Snake Case methods

camelCase Methods

this_comp

this_layer

this_property

color_depth

has_parent

in_point

out_point

start_time

has_video

has_audio

audio_active

anchor_point

audio_levels

time_remap

casts_shadows

light_transmission

accepts_shadows

accepts_lights

frame_duration

shutter_angle

shutter_phase

num_layers

pixel_aspect

point_of_interest

depth_of_field

focus_distance

blur_level

cone_angle

cone_feather

shadow_darkness

shadow_diffusion

active_camera

thisComp

thisLayer

thisProperty

colorDepth

hasParent

inPoint

outPoint

startTime

hasVideo

hasAudio

audioActive

anchorPoint

audioLevels

timeRemap

castsShadows

lightTransmission

acceptsShadows

acceptsLights

frameDuration

shutterAngle

shutterPhase

numLayers

pixelAspect

pointOfInterest

depthOfField

focusDistance

blurLevel

coneAngle

coneFeather

shadowDarkness

shadowDiffusion

activeCamera

value_at_time()

velocity_at_time()

speed_at_time()

nearest_key()

posterize_time()

look_at()

seed_random()

gauss_random()

ease_in()

ease_out()

rgb_to_hsl()

hsl_to_rgb()

degrees_to_radians()

radians_to_degrees()

from_comp_to_surface()

to_comp_vec()

from_comp_vec()

to_world_vec()

from_world_vec()

to_comp()

from_comp()

to_world()

from_world()

temporal_wiggle()

loop_in_duration()

loop_out_duration()

loop_in()

loop_out()

valueAtTime()

velocityAtTime()

speedAtTime()

nearestKey()

posterizeTime()

lookAt()

seedRandom()

gaussRandom()

easeIn()

easeOut()

rgbToHsl()

hslToRgb()

degreesToRadians()

radiansToDegrees()

fromCompToSurface()

toCompVec()

fromCompVec()

toWorldVec()

fromWorldVec()

toComp()

fromComp()

toWorld()

fromWorld()

temporalWiggle()

loopInDuration()

loopOutDuration()

loopIn()

loopOut()

Using eval() with binary-encoded (.jsxbin) expressions

Expressions encoded in the ExtendScript binary format (saved as a binary .jsxbin file from ExtendScript ToolKit CC) are not supported by the JavaScript engine.

If you want to obfuscate an expression, use the Legacy ExtendScript engine or use a different obfuscation method that is compatible with ECMAScript 2018. Some obfuscation methods might not be compatible with both expression engines.

Limited support for the $. (Dollar) object

The $. (Dollar) object methods and properties are specific to ExtendScript and mostly not supported in the JavaScript engine. This table lists unsupported and supported uses of the $. (Dollar) object:

Unsupported $.

Supported $.

$.fileName

$.hiResTimes

$.stack

$.evalFile()

$.list()

$.setenv()

$.getenv()

$.appEncoding

$.buildDate

$.decimalPoint

$.dictionary

$.error

$.flags

$.includePath

$.level

$.line

$.locale

$.localize

$.memCache

$.os

$.screens

$.strict

$.version

$.build

$.engineName (this is unsupported in the Legacy ExtendScript engine)

$.global

No support for ...reflect.properties, ...reflect.methods, and toSource()

reflect.properties and reflect.methods are not supported in the JavaScript engine; they are ExtendScript-specific methods which have no direct equivalent in JavaScript.

toSource() in JavaScript has been deprecated and is not part of any standards track.

To see a list of available properties and methods for any given After Effects property similar to what was provided by the above methods, use the following expression on a Source Text property and link it to your desired property, for example, using the pick whip in place of thisProperty on line 1:

let obj = thisProperty; // Replace "thisProperty" with a property-link to your desired property.
let props = [];
do {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (props.indexOf(prop) === -1) {
props.push(prop);
}
});
} while (obj = Object.getPrototypeOf(obj));
props.join("\n"); // Returns an array of strings listing the properties and methods available.
let obj = thisProperty; // Replace "thisProperty" with a property-link to your desired property. let props = []; do { Object.getOwnPropertyNames(obj).forEach(prop => { if (props.indexOf(prop) === -1) { props.push(prop); } }); } while (obj = Object.getPrototypeOf(obj)); props.join("\n"); // Returns an array of strings listing the properties and methods available.
let obj = thisProperty;     // Replace "thisProperty" with a property-link to your desired property.
let props = [];
 
do {
    Object.getOwnPropertyNames(obj).forEach(prop => {
        if (props.indexOf(prop) === -1) {
            props.push(prop);
        }
    });
} while (obj = Object.getPrototypeOf(obj));
 
props.join("\n");           // Returns an array of strings listing the properties and methods available.

The above expression is not compatible with the Legacy ExtendScript engine. It uses syntax and methods not available in ECMAScript 3.

Syntax Requirements for .jsx Expression Libraries and eval() with the JavaScript Engine

When using expressions inside a .jsx expression function library or when an expression is called inside eval(), certain syntax needs to be modified:

An explicit thisLayer. or thisProperty. prefix needs to be added to any native method or attribute which is not explicitly called on a layer or property. The prefix tells the JavaScript engine which object you are calling the method or attribute on.

Math operations on array values like Position needs to be calculated using vector math or by using looping functions to act on every item in the array. Overloaded math operators like position + [100,100] will not evaluate.

When using the JavaScript engine, expressions are pre-processed before evaluation in order to make some of the Legacy ExtendScript expression syntax readable by the new engine. However, these pre-processing tasks are not performed when evaluating expressions from a .jsx expression function library or when an expression is called inside eval(). The above syntax modifications must be made manually for these cases. All of these syntax modifications are backward-compatible with the Legacy ExtendScript engine, so a .jsx expression library written to work with the JavaScript engine will also work with the Legacy ExtendScript engine.

Performance tip: Due to the lack of pre-processing, calling complex expressions from a .jsx library with this syntax and the JavaScript engine may see a performance improvement, compared to calling the same expression directly on a property.

Explicitly prefix native methods and attributes with thisLayer. or thisProperty.

The following table lists the methods and attributes that require a prefix. For example, time must be written as thisLayer.time, while wiggle() must be written as thisProperty.wiggle()

These prefixes are only required in cases when the attribute or method isn't already being explicitly called on another layer or property. For instance, when calling thisComp.layer(1).hasParent, adding thisLayer. is not required because .hasParent is already being explicitly called on layer(1).

Methods Requiring thisLayer.

Attributes Requiring thisLayer.

Methods Requiring thisProperty.

Attributes Requiring thisProperty.

comp()
footage()
posterizeTime()
add()
sub()
mul()
div()
clamp()
length()
dot()
normalize()
cross()
lookAt()
timeToFrames()
framesToTime()
timeToTimecode()
timeToFeetAndFrames()
timeToNTSCTimecode()
timeToCurrentFormat()
seedRandom()
random()
gaussRandom()
noise()
degreesToRadians()
radiansToDegrees()
linear()
ease()
easeIn()
easeOut()
rgbToHsl()
hslToRgb()
hexToRgb()
mask()
sourceRectAtTime()
sourceTime()
sampleImage()
toComp()
fromComp()
toWorld()
fromWorld()
toCompVec()
fromCompVec()
toWorldVec()
fromWorldVec()
fromCompToSurface()

time
source
thisProject
colorDepth
transform
anchorPoint
position
scale
rotation
opacity
orientation
rotationX
rotationY
rotationZ
lightTransmission
castsShadows
acceptsShadows
acceptsLights
ambient
diffuse
specular
specularIntensity
shininess
specularShininess
metal
audioLevels
timeRemap
marker
name
width
height
index
parent
hasParent
inPoint
outPoint
startTime
hasVideo
hasAudio
active
enabled
audioActive
cameraOption
pointOfInterest
zoom
depthOfField
focusDistance
aperature
blurLevel
irisShape
irisRotation
irisRoundness
irisAspectRatio
irisDiffractionFringe
highlightGain
highlightThreshold
highlightSaturation
lightOption
intensity
color
coneAngle
coneFeather
shadowDarkness
shadowDiffusion

valueAtTime()
velocityAtTime()
speedAtTime()
wiggle()
temporalWiggle()
smooth()
loopIn()
loopOut()
loopInDuration()
loopOutDuration()
key()
nearestKey()
propertyGroup()
points()
inTangents()
outTangents()
isClosed()
pointsOnPath()
tangentOnPath()
normalOnPath()
createPath()

velocity
speed
numKeys
propertyIndex

Replacing math operators with vector math functions

Both the JavaScript and LegacyExtendScript engines allow overloading math operators for arrays by using syntax like position + [100,100], but this does not work for expressions inside a .jsx expression function library or inside eval().

In order to perform math on array properties like Position, Scale, etc., the vector math equivalents should be used for addition, subtraction, multiplication, and division. The vector math functions will also work for regular numbers, so a function which might be called on properties of either data type should use the vector math functions.

Opmerking:

The thisLayer. prefix must be used with the vector math functions.

  • Addition: thisLayer.add(vec1, vec2)
  • Subtraction: thisLayer.sub(vec1, vec2)
  • Multiplication: thisLayer.mul(vec, amount)
  • Division: thisLayer.div(vec, amount)

Below are examples of expressions using standard math and updated vector math. The vector math expressions also use the appropriate thisLayer. or thisProperty. prefix when needed.

To find the difference between a wiggle() and the value of a Position property:

// Standard Math:
wiggle() - value;
// Vector Math:
thisLayer.sub( thisProperty.wiggle(), value );
// Standard Math: wiggle() - value; // Vector Math: thisLayer.sub( thisProperty.wiggle(), value );
// Standard Math:
wiggle() - value;
 
 
// Vector Math:
thisLayer.sub( thisProperty.wiggle(), value );

To interpolate between two values, similar to linear(), but with an extended range beyond the defined minimum and maximum:

// Standard Math:
value1 + ( ( t - tMin ) / ( tMax - tMin ) ) * ( value2 - value1 );
// Vector Math:
thisLayer.add( value1, thisLayer.mul( thisLayer.div( thisLayer.sub( t, tMin ), thisLayer.sub( tMax, tMin ) ), thisLayer.sub( value2, value1 ) ) );
// Standard Math: value1 + ( ( t - tMin ) / ( tMax - tMin ) ) * ( value2 - value1 ); // Vector Math: thisLayer.add( value1, thisLayer.mul( thisLayer.div( thisLayer.sub( t, tMin ), thisLayer.sub( tMax, tMin ) ), thisLayer.sub( value2, value1 ) ) );
// Standard Math:
value1 + ( ( t - tMin ) / ( tMax - tMin ) ) * ( value2 - value1 );
 
 
// Vector Math:
thisLayer.add( value1, thisLayer.mul( thisLayer.div( thisLayer.sub( t, tMin ), thisLayer.sub( tMax, tMin ) ), thisLayer.sub( value2, value1 ) ) );

To loop a Position property in and out:

// Standard Math:
loopIn( "cycle" ) + loopOut( "cycle" ) - value;
// Vector Math:
thisLayer.sub( thisLayer.add( thisProperty.loopIn( "cycle" ), thisProperty.loopOut( "cycle" ) ), value );
// Standard Math: loopIn( "cycle" ) + loopOut( "cycle" ) - value; // Vector Math: thisLayer.sub( thisLayer.add( thisProperty.loopIn( "cycle" ), thisProperty.loopOut( "cycle" ) ), value );
// Standard Math:
loopIn( "cycle" ) + loopOut( "cycle" ) - value;
 
 
// Vector Math:
thisLayer.sub( thisLayer.add( thisProperty.loopIn( "cycle" ), thisProperty.loopOut( "cycle" ) ), value );

Krijg sneller en gemakkelijker hulp

Nieuwe gebruiker?