Velo Example: Coding a More Robust Custom Element

With Velo, you can add special behaviors and additional functionality to custom elements that you added to your site. First you create and code the element and its behavior in a JavaScript file. Then implement how your site should handle events on the custom element with Velo. 

This article assumes here you know a bit about designing elements with CSS properties in JavaScript and working with web components. This article provides an overview of the code to get you started, providing links and pointers along the way. 

What This Article Will Demonstrate

In this article, we'll use this company logo as our custom element:

In this example, we'll learn how to change the logo's behavior when visitors perform actions on our site, such as making the logo spin, resize, and shake. 

Display a Custom Element

Review this basic example to learn how to display a logo. The example displays the logo without any additional changes to the logo's behavior. 

Note: This article provides basic instructions for creating custom elements. For full instructions and examples, see the MDN documentation.

Here is the sample code provided in the basic example: 

Copy
1
const LOGO_TXT = 'https://static.wixstatic.com/media/logo-txt.png';
2
const LOGO_IMG = 'https://static.wixstatic.com/media/logo-img.png';
3
4
const createImg = (id, src) => {
5
const img = document.createElement('img');
6
img.src = src;
7
img.id = id;
8
img.width = '200';
9
img.style.position = 'fixed';
10
return img;
11
}
12
13
class myLogoDisplay extends HTMLElement {
14
constructor() {
15
super();
16
}
17
18
connectedCallback() {
19
this.appendChild(createImg('txt', LOGO_TXT));
20
this.appendChild(createImg('img', LOGO_IMG));
21
}
22
23
}
24
25
customElements.define('my-logo-display', myLogoDisplay);

To review:

  • We created variables for the different parts of the logo custom element. We separated the logo into two images so that we can manipulate each one separately.
  • We used the connectedCallback() function to display the logo images when the page is connected and ready.
  • We registered the custom element with customElements.define().

Add the Custom Element to Your Site

Because this example uses Velo, we can use it to host our custom element.

  1. In the Public & Backend section of the Code sidebar (Wix Studio), or the Velo sidebar (Wix Editor), upload the the custom element's JavaScript file to the Public > custom-elements folder.
  2. Add the custom element to our page. 
  3. In Settings, choose Velo File and select the file you uploaded.

Make sure the custom element displays on the site.

Modify the Behavior of the Custom Element

Now we'll update our custom element so that it behaves differently when site visitors interact with the page. 

For example, if a visitor scrolls down, the logo spins, and the text gets smaller until it disappears.

We can also set up the custom element to behave differently if a site visitor interacts with some other element on the page. 

For example, if a visitor hovers over a different element on the page, such as a menu, the logo shakes.

Implement the Behaviors on Your Server

This article assumes here you know a bit about designing elements with CSS properties in JavaScript and working with web components. This article provides an overview of the code to get you started, providing links and pointers along the way. 

Set Up Variables and Styles

We first create three images that together comprise the company logo: LOGO_TXT, LOGO_IMG, and LOGO_PHONE. By separating the logo into three parts, each part of the logo can be controlled separately. Each of these images are stored in the Wix Media Gallery.

We also create a STYLE variable that defines custom element behaviors using CSS properties.

  • The animation property sets multiple animations at one time as a shortcut.
  • The animation-iteration-count property sets how many times an animation should repeat.
  • The transform property is for rotating and resizing elements.
Copy
1
const LOGO_TXT = 'https://static.wixstatic.com/media/logo-txt.png';
2
const LOGO_IMG = 'https://static.wixstatic.com/media/logo-img.png';
3
const LOGO_PHONE ='https://static.wixstatic.com/media/logo-phone.png';
4
5
const STYLE = `
6
#img.shake {
7
animation: shake 0.5s;
8
animation-iteration-count: infinite;
9
}
10
11
@keyframes shake {
12
0% { transform: translate(1px, 1px) rotate(0deg); }
13
10% { transform: translate(-1px, -2px) rotate(-1deg); }
14
20% { transform: translate(-3px, 0px) rotate(1deg); }
15
30% { transform: translate(3px, 2px) rotate(0deg); }
16
40% { transform: translate(1px, -1px) rotate(1deg); }
17
50% { transform: translate(-1px, 2px) rotate(-1deg); }
18
60% { transform: translate(-3px, 1px) rotate(0deg); }
19
70% { transform: translate(3px, 1px) rotate(-1deg); }
20
80% { transform: translate(-1px, -1px) rotate(1deg); }
21
90% { transform: translate(1px, 2px) rotate(0deg); }
22
100% { transform: translate(1px, -2px) rotate(-1deg); }
23
}
24
`;

Define Functions

Rotate and Resize the Custom Element

The scrollHandler() function rotates the logo and changes its size.

Copy
1
const scrollHandler = root => (event) => {
2
const txt = root.querySelector('#txt');
3
const scaleTxt = (Math.max(0, 1000 - window.scrollY)) / 1000;
4
txt.style.transform = `scale(${scaleTxt})`;
5
dispatchSizeChange(root, scaleTxt);
6
const opacityTxt = 1 / Math.log10(Math.max(10, window.scrollY));
7
txt.style.opacity = opacityTxt;
8
const img = root.querySelector('#img');
9
img.style.transform = `rotate(${window.scrollY / 10}deg)`;
10
}

Check if the Size of the Custom Element Has Changed

In our example, as we scroll up or down, our logo changes size.

This function checks if the element is getting smaller or bigger, and fires a sizeChange event for Velo to handle. The dispatchEvent() function creates the new custom event. The event is triggered when the size of the custom element gets bigger or smaller.

Later, when we code in Velo, we use the Velo API on() function to handle the sizeChange event.

Copy
1
const dispatchSizeChange = (root,scaleTxt) => {
2
const gettingSmall = scaleTxt === 0 && root.prevScaleTxt !== 0;
3
const gettingBig = scaleTxt !== 0 && root.prevScaleTxt === 0;
4
root.prevScaleTxt = scaleTxt;
5
6
// Use dispatchEvent() to create a new custom event that
7
// Velo can handle. In our example, the event is
8
// triggered when the size of the custom element gets bigger or
9
// smaller.
10
if (gettingSmall || gettingBig) {
11
root.dispatchEvent(new CustomEvent('sizeChange',
12
{ detail: { data: gettingSmall ? 'small' : 'big' } }));
13
}
14
};

Add a logo image to the page

The createImg() function takes each part of the logo and adds it to the page. This is equivalent to adding the image to the page's DOM.

Copy
1
// Create each image that the logo comprises and
2
// add it to the DOM using a createImg function.
3
const createImg = (id, src) => {
4
const img = document.createElement('img');
5
img.src = src;
6
img.id = id;
7
img.width = '200';
8
img.style.position = 'fixed';
9
return img;
10
}

Create the Custom Element Class

This code defines the allEffects custom element class.

  • The class is designed to listen for the mouse wheel to indicate the page is being scrolled, and calls the scrollHandler() function using the style we defined earlier. In this example, if the visitor scrolls to the bottom of the page, the logo gets smaller. If the visitor scrolls to the top, the logo gets larger.

  • Classes can also contain lifecycle callback functions. Lifecycle callback functions are triggered at specific, predetermined points. This code uses the connectedCallback() function, which is called when the custom element is available for rendering on the site. At this point, our custom element is displayed on the page, without the phone number.

  • Another lifecycle callback function used in this code is the attributeChangedCallback() function. This function is called whenever an attribute of the custom element is added, removed, or changed. This function works together with a static get observedAttributes() method to determine what attributes changed. In this code, we check if the site visitor's mouse is hovering over the menu ("hoveringmenu"), and we check if the footer is currently displayed in the site visitor's viewport ("footershown"). If the mouse is over the menu, our logo shakes. If the footer is in view, the phone number is not shown.

Copy
1
class allEffects extends HTMLElement {
2
constructor() {
3
super();
4
document.addEventListener('wheel', scrollHandler(this), {
5
capture: false, passive: true });
6
}
7
8
connectedCallback() {
9
this.appendChild(createImg('txt', LOGO_TXT));
10
this.appendChild(createImg('img', LOGO_IMG));
11
const phone = createImg('phone', LOGO_PHONE);
12
phone.style.display = 'none';
13
this.appendChild(phone);
14
15
const style = document.createElement('style');
16
style.innerHTML = STYLE;
17
this.appendChild(style);
18
}
19
20
attributeChangedCallback(name, oldValue, newValue) {
21
if (name === 'hoveringmenu') {
22
this.querySelector('#img').className = newValue === 'true' ? 'shake' : '';
23
}
24
if (name === 'footershown') {
25
this.querySelector('#phone').style.display = newValue === 'true' ? '' : 'none';
26
}
27
}
28
29
static get observedAttributes() {
30
return ['hoveringmenu', 'footershown'];
31
}
32
}

Register the Custom Element

This part of the code actually defines the custom element that we specified using the parameters, functions, and class above. Use the customElements.define method to register the custom element class allEffects, and associate the custom element with the tag name for the custom element in the Velo representation of the custom element,my-logo-effects.

Copy
1
customElements.define('my-logo-effects', allEffects);

The Custom Element Complete Code

For your convenience, here is the custom element's code in one code snippet: 

Copy
1
const LOGO_TXT = 'https://static.wixstatic.com/media/logo-txt.png';
2
const LOGO_IMG = 'https://static.wixstatic.com/media/logo-img.png';
3
const LOGO_PHONE ='https://static.wixstatic.com/media/logo-phone.png';
4
5
const STYLE = `
6
#img.shake {
7
animation: shake 0.5s;
8
animation-iteration-count: infinite;
9
}
10
11
@keyframes shake {
12
0% { transform: translate(1px, 1px) rotate(0deg); }
13
10% { transform: translate(-1px, -2px) rotate(-1deg); }
14
20% { transform: translate(-3px, 0px) rotate(1deg); }
15
30% { transform: translate(3px, 2px) rotate(0deg); }
16
40% { transform: translate(1px, -1px) rotate(1deg); }
17
50% { transform: translate(-1px, 2px) rotate(-1deg); }
18
60% { transform: translate(-3px, 1px) rotate(0deg); }
19
70% { transform: translate(3px, 1px) rotate(-1deg); }
20
80% { transform: translate(-1px, -1px) rotate(1deg); }
21
90% { transform: translate(1px, 2px) rotate(0deg); }
22
100% { transform: translate(1px, -2px) rotate(-1deg); }
23
}
24
`;
25
26
const scrollHandler = root => (event) => {
27
const txt = root.querySelector('#txt');
28
const scaleTxt = (Math.max(0, 1000 - window.scrollY)) / 1000;
29
txt.style.transform = `scale(${scaleTxt})`;
30
dispatchSizeChange(root, scaleTxt);
31
const opacityTxt = 1 / Math.log10(Math.max(10, window.scrollY));
32
txt.style.opacity = opacityTxt;
33
const img = root.querySelector('#img');
34
img.style.transform = `rotate(${window.scrollY / 10}deg)`;
35
}
36
37
const dispatchSizeChange = (root,scaleTxt) => {
38
const gettingSmall = scaleTxt === 0 && root.prevScaleTxt !== 0;
39
const gettingBig = scaleTxt !== 0 && root.prevScaleTxt === 0;
40
root.prevScaleTxt = scaleTxt;
41
42
if (gettingSmall || gettingBig) {
43
root.dispatchEvent(new CustomEvent('sizeChange',
44
{ detail: { data: gettingSmall ? 'small' : 'big' } }));
45
}
46
};
47
48
const createImg = (id, src) => {
49
const img = document.createElement('img');
50
img.src = src;
51
img.id = id;
52
img.width = '200';
53
img.style.position = 'fixed';
54
return img;
55
}
56
57
class allEffects extends HTMLElement {
58
constructor() {
59
super();
60
document.addEventListener('wheel', scrollHandler(this), {
61
capture: false, passive: true });
62
}
63
64
connectedCallback() {
65
this.appendChild(createImg('txt', LOGO_TXT));
66
this.appendChild(createImg('img', LOGO_IMG));
67
const phone = createImg('phone', LOGO_PHONE);
68
phone.style.display = 'none';
69
this.appendChild(phone);
70
71
const style = document.createElement('style');
72
style.innerHTML = STYLE;
73
this.appendChild(style);
74
}
75
76
attributeChangedCallback(name, oldValue, newValue) {
77
if (name === 'hoveringmenu') {
78
this.querySelector('#img').className = newValue === 'true' ? 'shake' : '';
79
}
80
if (name === 'footershown') {
81
this.querySelector('#phone').style.display = newValue === 'true' ? '' : 'none';
82
}
83
}
84
85
static get observedAttributes() {
86
return ['hoveringmenu', 'footershown'];
87
}
88
}
89
90
customElements.define('my-logo-effects', allEffects);

Code the Behaviors with Velo

Custom element behaviors are coded both in the custom elements' JavaScript file and also in Velo. 

You can customize how the custom element behaves for a single page on the site or for all pages on the site. 

On a Single Page

In our example, use the on() function to define what happens when the sizeChange event is triggered.

As we scroll down the current page:

  • The logo gets smaller, as defined in the custom element's JavaScript file.
  • The menu disappears, as defined here in Velo.

We defined this sizeChange event in the JavaScript file that contains our custom element's implementation in Line 11 above: new CustomEvent('sizeChange');

Creating events is the custom element's way of "communicating" with Velo. Events let Velo know that something has to be handled, and parameters can be sent with the event.

Copy
1
$w.onReady(function () {
2
$w('#myCustomElement').on('sizeChange', ({detail: {data}}) => {
3
if (data === 'small') {
4
$w('#menu').hide();
5
} else {
6
$w('#menu').show();
7
}
8
})
9
});

On All Site Pages

If we want the behavior to apply to all pages of the site, we code custom element behaviors in the masterPage.js file located in the Page Code section of the Code sidebar (Wix Studio), or the Velo sidebar (Wix Editor).

In the following code, we create events for the actions that a site visitor might perform that impact our custom element. 

We then create and set attributes on the custom element using setAttribute(). This is Velo's way of "communicating" with the custom element. We can pass the custom element attributes and their values so that the custom element's implementation can behave accordingly.

In the code snippet below, we see that the custom element's behavior will change for the entire site in the following cases: 

  • If the footer enters or leaves the viewport.
  • If the site visitor's mouse hovers or stops hovering over the menu.
Copy
1
$w.onReady(function () {
2
$w('#footer').onViewportEnter((event) => {
3
$w('#myCustomElement').setAttribute('footershown', true);
4
});
5
6
$w('#footer').onViewportLeave((event) => {
7
$w('#myCustomElement').setAttribute('footershown', false);
8
});
9
10
$w('#menu').onMouseIn((event) => {
11
$w('#myCustomElement').setAttribute('hoveringmenu', true);
12
});
13
14
$w('#menu').onMouseOut((event) => {
15
$w('myCustomElement').setAttribute('hoveringmenu', false);
16
});
17
});
Was this helpful?
Yes
No