David Unaipon Ngarrindjeri man and early hacker

The global object in After Effects

It’s all about the $.

One of the undocumented expression language feature in After Effects is the global object, also known as $. And it turns out that $ can bring you happiness…

$ is a mysterious “Helper Object” that is available to extendscript and used to do things like find the current version of the application, or what the user’s name is. But it also appears to be available for use in expressions. This turns out to be very handy.

I’ve been researching the $ object and its members, but apart from accidentally discovering that there’s a blink() function for strings (hilariously it works, but not in the way that anyone who ever had a geocites account thinks), I haven’t found too much to get excited about.

In the scripting world, for example $._ADBE_LIBS_CORE.getCurrentUser gives you all sorts of data about the current user’s Creative Cloud account. That doesn’t work in expressions. So what is $ actually good for?

Think Globally

Because $ is a global object, it’s available to all expressions. And since it’s Javascript we can just hot-glue properties and methods on to it whenever we like. Here’s an example: the source text of the text layer is controlled by a variable on the solid layer.

screenshot of After Effects showing expressions

So on the solid layer we have this expression:

$.myVar = "It Works!"
value

In the above code we assign a property to the global variable just by initialising it. I always find it weird how you can do that, but you can. So now the $ global variable has a property called myVar which has the value "It Works!".

The next line, value is just there to avoid an expression error, it simply sets the value of the property to whatever the property was going to be anyway. You can see it’s on the position property, but the function we define has nothing to do with position, in fact it returns a string of text.

On the text layer we have this expression:

if($.myVar){$.myVar} else {"nope"}

This checks to see that $.myVar has been defined and has a value (other than null, false or 0) and if so, it uses it as the source text of the text layer. If myVar was not defined the text layer would say nope.

This doesn’t do anything radically different than you could have achieved using expression control effects and a lot of pick-whipping, but it would be super convenient for global styles for a whole project. Say you had a colour pallette and you wanted to be able to update it easily. Just create an object like this

$.myStyle = {base: 
        {fill: [123,45,67], 
        stroke: [23,45,6],
        lineWidth: 3},
    accent: {fill: [255,128,12],
        stroke: [23,45,6],
        lineWidth: 3}
}

And of course you could make the global style object as complicated as you like: specify as many properties as you like, then call them up from expressions like this:

$.mystyle.base.fill; // returns [123,45,67]

It’s kinda like CSS for AE. I originally didn’t think it was that useful, but that seems like a pretty exciting development. But here’s where I think it gets really fun (ok, I’ve got a low threshold for my definition of fun):

Here comes the Func

Because you can pass a function as a variable in JavaScript, you can do something like this:

multiple text layers in after effects controlled by a single function

Here, the variable myFunc is a function that takes one parameter foo. All of the text layers are duplicates of each other, but because the function is executed independently on each one, it returns different results. This is the expression that creates the global function:

$.myFunc = function(foo){
	seedRandom(foo.index, timeless=true);
	return "index: " + foo.index + 
	", red solid index: " +	index + 
	", random: " + Math.round( Math.random(index* 100));
	}
value

The parameter foo is expecting a reference to a layer (I should have named it better, I know) and returns a string with: the index of the layer referred to by foo (specified by foo.index), the index of the layer where the function is defined (just index), and a random number.

In the text layer the variable foo is assigned the value thisLayer which is a reference to the text layer itself. So foo.index equates to thisLayer.index. This is necessary because if we just used index as demonstrated by the next line, it returns the index of the layer where the function is defined. So this is important. Any function that uses properties of a layer needs to be explicitly told what layer it’s referring to.

Similarly the random number is different on each one because the seed is foo.index, which evaluates differently on each layer.

One function to rule them all

So as you can see each layer is evaluating the function separately to achieve different results—but they’re all using the same function. If I change the expression on the red solid they will all update their behaviour instantly without me having to copy and paste new expressions. This is a massive time saver for procedural animation where you might have scads of layers with hard-to-get-at properties controlled by expressions.

Some caveats

  • This is a rather experimental approach, and may get patched out of existence in updates of AE, so don’t rely on it to fly your aeroplane or run your nuclear weapons system.
  • If you use an expression like $.myVar = time you would expect $.myVar to equal the current time of the composition. Not so. The value of $.myVar in that code is set at the point the expression is evaluated, and not refreshed when the playhead moves. If you want to use time you’ll have to use it in a function. A trivial example: $.compTime = function(){return time}
  • variables persist when they’re deleted or renamed. At least until you close After Effects. This can cause weirdness and means that you could accidentally delete a variable that you’re using in another layer and it will keep working until the next time you open up the project, when it will be mysteriously broekn.

Apendix

This page does mention the $ object and notes that there are three supported uses,

  • $.build – returns the build of After Effects,
  • $.engineName – the name of the expression engine, which in the current build turns out to be torq/v8, the Javascript engine from Chromium, surprise surprise and
  • $.global – which obviously needs further investigation

So if I look at the members of $.global this is what I get:

Object, Function, Array, Number, parseFloat, parseInt, Infinity, NaN, undefined, Boolean, String, Symbol, Date, Promise, RegExp, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, JSON, Math, console, Intl, ArrayBuffer, Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array, Uint8ClampedArray, DataView, Map, Set, WeakMap, WeakSet, Proxy, Reflect, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, escape, unescape, eval, isFinite, isNaN, SharedArrayBuffer, Atomics, WebAssembly, thisComp, value, effect, ProxyBase, _makeProxy, __add, __sub, __mul, __div, __unprepareContext, __prepareContext, Comp, Footage, Group, Layer, Camera, Light, Effect, Mask, Property, PathProperty, TextProperty, MarkerProperty, Key, MarkerKey, Custom, Project, $, thisLayer, thisProperty, __internal_expression_state, constructor, __defineGetter_, _defineSetter_, hasOwnProperty, _lookupGetter_, _lookupSetter_, isPrototypeOf, propertyIsEnumerable, toString, valueOf, __proto, toLocaleString

I got all hot and bothered when I saw console in there, and using $.global.console.log("hello"); works, in that it doesn’t throw an error, but I can’t find where the console it logs to is. It’s not the text in the info window like I was hoping, and it’s not the console in ESTK. Drat.

Interestingly all the variables that I’ve defined in any expression turn up inside the $.global object, except if they were defined with let. However calling these variables like $.global.myvar doesn’t return anything.

to be continued…

If you’re wondering who the image at the top of the page is, it’s David Unaipon, Ngarrindjeri man, Australia’s Da Vinci, and old-school hacker. He’ll be recognisable to Australians as the bloke on the $50 note, hence the connection: hacker and dollars. This blog is written on the stolen lands of the Wurundjeri people of the Kulin Nation, where sovereignty was never ceded. Always was, always will be.

5 comments

  1. What version of AE are you using? I’m running 2019 and not able to set variables on the global object, using the $ operator.

    Reply

    1. Currently running 17.0.1, AKA 2020. It requires the new JS engine, I’m not sure exactly when that was introduced.

      Reply

  2. Many thanks for sharing this STIB. I’ve just started with the basics but I’ll be digging into this a lot more. (It works fine in AE 16 )

    Reply

  3. This can not consistently work, you might get lucky and it might work in simple cases, but once you start mixing expressions dependent on time-varying streams with this so called global, nonsensical things will start to happen
    One of the main reasons this does not work, is because we may have multiple JS engines with their own app($) objects, so you might set the variable in one engine and not see it in other. The primary scenario for creating multiple JS engines is when a property’s expression depends on aother property’s expression, which is when this starts breaking. You see the breakage even sooner if properties are time varying, because then time-variant cache can’t hide the frailness of the situation.
    Creating variables is one thing, but creating functions like this is worse because all the setup we do to automatically allow thisLayer, thisProperty etc. wont work among other things.
    The correct way to do globals is to create them outside of the properties (to avoid evaluation order issues, lets say a console command) then prime all engines with them, and invalidate caches/re-prime them on updates etc. And probably much more than this, but requires quite a bit of thought.

    Reply

  4. Ouch – seems the _ADBE_LIBS_CORE part of $ is missing from version 17.1.1 – it turns up as undefined.
    Now, how to log to a file from a JSX file gets really muddy…
    I’m off to doing it with a system call and just echo into a file there, but it’s not a nice thing to miss.

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.