Skip to main content

Combo

Combo provides a convenient way to define composite Incidents, consisting of many (and of different types) Incidents, positioned on specific milliseconds within the Combo.

Example

import { HTMLClip, Combo, CSSEffect } from "@donkeyclip/motorcortex";

// Clip definition
const myClip = new HTMLClip({
host: document.getElementById("app"),
html: `
<div>
<div class="super-container first">
<div class="container">
<div class="circle circle-a"></div>
<div class="circle circle-b"></div>
<div class="circle circle-c"></div>
<div class="text-container">{{ initParams.text }}</div>
</div>
</div>
<div class="super-container second">
<div class="container">
<div class="circle circle-a"></div>
<div class="circle circle-b"></div>
<div class="circle circle-c"></div>
<div class="text-container">{{ initParams.secondText }}</div>
</div>
</div>
</div>
`,
css: `
.container{
position: relative;
width: 600px;
height: 400px;
font-family: 'Ubuntu', sans-serif;
text-align: center;
text-shadow: 2px 2px 2px #444;
background: whitesmoke;
}

.circle{
transform: scale(0);
opacity: 0.7;
position: absolute;
border-radius: 100%;
}

.circle-a{
top: 2%;
left: 15%;
width: 390px;
height: 390px;
background: #f72585;
}

.super-container.second .circle-a{
background: #D4C1EC;
}

.circle-b{
top: 18%;
left: 10%;
width: 320px;
height: 320px;
background: #7209b7;
}

.super-container.second .circle-b{
background: #9F9FED;
left: 36%;
}

.circle-c{
top: 11%;
right: 15%;
width: 210px;
height: 210px;
background: #3a0ca3;
}

.super-container.second .circle-c{
background: #736CED;
right: 50%;
}

.text-container{
transform: scale(10);
opacity: 0;
position: absolute;
top: 55%;
left: 15%;
color: whitesmoke;
font-size: 26px;
}

.second .text-container{
left: 55%;
}
`,
fonts: [
{
type: "google-font",
src:
"https://fonts.googleapis.com/css2?family=Ubuntu:wght@500;700&display=swap"
}
],
containerParams: {
width: "600px",
height: "800px"
},
initParams: {
text: "Hello MotorCortex!",
secondText: "Have fun!!"
}
});

// Combo Definition
const myCombo = new Combo(
{
incidents: [
{
incidentClass: CSSEffect,
attrs: {
animatedAttrs: {
transform: {
scaleX: 1,
scaleY: 1
},
opacity: 1
}
},
props: {
selector: ".text-container",
duration: 1200,
easing: "easeInBack"
},
position: 0
},
{
incidentClass: CSSEffect,
attrs: {
animatedAttrs: {
transform: {
scaleX: 1,
scaleY: 1
}
}
},
props: {
selector: ".circle",
duration: 1200,
delay: "@stagger(0, 600, 0, easeInCirc)",
easing: "easeOutBounce"
},
position: 900
}
]
},
{
selector: ".super-container",
delay: "@stagger(0, 800)"
}
);

// Add Combo to Clip
myClip.addIncident(myCombo, 0);

// Play the Clip
myClip.play();

API

The Combo Incident takes both attrs and props.

attrs

The attrs have only one key, the “incidents” which is a collection of Incidents that form the Combo. These incidents are called the “internal Incidents”. Each internal Incident must have the following keys:

  • incidentClass: the class of the Incident we want to position on our Combo
  • attrs: the attributes of the Incident
  • props: the props of the Incident
  • position: the position of the Incident in the Combo, as always in milliseconds

props

The props accepted by a Combo are the following:

  • selector: the selector to apply the Combo
  • delay: the delay of the Combo in milliseconds
  • hiatus: the hiatus of Combo in milliseconds
  • repeats: the number of repeats (default = 0)

The selector of the Combo’s props defines the target elements of the Combo. For each of these elements the full Combo is applied. If the internal Incidents do not have a selector then they directly refer to the Combo’s element. If they have a selector then they target the elements that apply to this selector and that are within the Combo element. In other words:

  • If we have a Combo with selector “.combo-element” and an internal Incident with no selector, the internal Incident is applied directly to “.combo-element”
  • If we have a Combo with selector “.combo-selector” and an internal Incident with selector “.internal” then the internal Incident applies on “.combo-selector .internal”

Combos advantages

delay, hiatus, repeats

Combos act as a fix Group of Incidents that can not be changed. Even though initially this doesn't seem to be an advantage it actually is. Because of its fix/close nature, Combo can accept properties such as delay, hiatus and repeats, something that is not doable on simple Groups.

dynamic position per element

The second advantage that a Combo provides is the position property of its Incidents.

A good idea is to get familiar with dynamic values first and come back to read this paragraph

As mentioned, when defining the Incidents that compose the Combo you set their position via the position property. As any other property the position property can accept dynamic values (such as @stagger, @expression, etc). This means that for an Incident (e.g. an Effect) defined on a Combo with dynamic position (e.g. position: '@stagger(0, 3000)') each of the elements of the Incident's selector will start on a different time. This can be achieved via the delay property as well. The difference between implementing this dynamic execution time via position compared to the delay is that delay is considered as part of the effect and thus no other Effect can run on the same elements and on the same attributes until all of the elements have finished animating.

The following example is only possible using Combos:

import { HTMLClip, CSSEffect, Combo } from "@donkeyclip/motorcortex";
import Player from "@donkeyclip/motorcortex-player";

const myClip = new HTMLClip({
id: "my-clip",
host: document.getElementById("clip"),
html: `
<div class="root">
<div class="title">Combo Example</div>
<div class="flex">
${(function fun() {
let boxWrapper = "";
for (let index = 0; index < 8; index++) {
boxWrapper += `<div class="box box${index} "></div>`;
}

return boxWrapper;
})()}
</div>
</div>`,
css: `
.root{
background:white;
width:100%;
height:100%;
font-family: 'Montserrat', sans-serif;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.title{
margin-bottom: 30px;
font-size: 50px;
color:#21084f;
}
.box-wrapper{
display:flex;
align-items:center;
justify-content:center;
justify-content: space-around;
height:300px
}

.box{
display: flex;
align-items: center;
justify-content: center;
justify-content: space-around;
height: 40px;
margin: 10px;
width:40px;
background: #21084f;
}
.flex{
display: flex;
justify-content: center;
}
`,
fonts: [
{
type: `google-font`,
src: `https://fonts.googleapis.com/css2?family=Montserrat:wght@700&display=swap`
}
],
containerParams: {
width: "600px",
height: "600px"
}
});

const myCombo = new Combo(
{
incidents: [
{
incidentClass: CSSEffect,
attrs: {
animatedAttrs: {
opacity: 0
}
},
props: {
duration: 1000
},
position: "@expression(index*150)"
},
{
incidentClass: CSSEffect,
attrs: {
animatedAttrs: {
opacity: 1
}
},
props: {
duration: 1000
},
position: "@expression(1000 + index*150)"
}
]
},
{
selector: ".box"
}
);

myClip.addIncident(myCombo, 0);

new Player({ clip: myClip });

Result

Plugins development

Combos are extendable and exposable by plugins. Once you reach the plugins development section you'll realise that it's easy to create complex / composite effects just by using Combos and expose them as ready to use Incidents, something that is only doable via Combos and only Combos.

Main differences with Group

The main differences of a Combo compared to a Group is that:

  • When defining a Group you add already initialised Incidents to it, so these Incidents continue to exist autonomously. You can edit these Incidents, remove them from the Group, place them on another Group and so on. On Combos that’s not the case. A Combo is a distinct / fix element and its internal Incidents are not directly accessible from the outside
  • Combos can accept delay, hiatus and repeats while Groups not