Introduction
This component consists in a card slider.
Let's start by creating the structure of our web component. Create a file cardLab.js:
class cardLab extends HTMLElement {
//Here will be all the functionality.
}
window.customElements.define('card-lab', cardLab);
Step 1: Add a constructor and a getter for noArrowsAt attribute
class cardLab extends HTMLElement {
constructor () {
super();
}
get noArrowsAt() {
return this.hasAttribute('noArrowsAt');
}
}
window.customElements.define('card-lab', cardLab);
Notice that noArrowsAt attribute is a breakpoint. If your card slider width is under or equal this value the arrows will be hidden.
Step 2: Add functions
class cardLab extends HTMLElement {
//Step 1
setSize(tot){
//setting the total width
this.shadowRoot.querySelector('.inner').style.width = tot + 'px';
var ex = this.shadowRoot.querySelector('.external').offsetWidth;
//always enable arrows
this.shadowRoot.querySelector('.prev').style.display = 'block';
this.shadowRoot.querySelector('.next').style.display = 'block';
//disabling arrows if dont need to scroll
if(tot <= ex){
this.shadowRoot.querySelector('.prev').style.display = 'none';
this.shadowRoot.querySelector('.next').style.display = 'none';
}
if(this.noArrowsAt){
if(ex <= this.getAttribute('noArrowsAt')){
this.shadowRoot.querySelector('.prev').style.display = 'none';
this.shadowRoot.querySelector('.next').style.display = 'none';
}
}
}
stripPx(value) {
if (value == "") return 0;
return parseFloat(value.substring(0, value.length - 2));
}
mount(){
//moving through the children cards
var cards = this.children;
var tot = 0;
for (var i = 0; i < cards.length; i++) {
//styling cards
cards[i].style.backgroundColor = 'white';
cards[i].style.cssFloat = 'left';
cards[i].style.boxShadow = '0 4px 8px 0 rgba(0,0,0,0.2)';
//finding total width
var w = this.stripPx(cards[i].style.width);
var m = this.stripPx(cards[i].style.marginRight);
tot = tot + w + m;
//calculating heights
this.shadowRoot.querySelector('.main').style.height = cards[i].offsetHeight + 50 + 'px';
this.shadowRoot.querySelector('.external').style.height = cards[i].offsetHeight + 20 + 'px';
this.shadowRoot.querySelector('.prev').style.top = -(cards[i].offsetHeight / 2) - 80 + 'px';
this.shadowRoot.querySelector('.next').style.top = -(cards[i].offsetHeight / 2) - 80 + 'px';
}
//setting the total width
this.setSize(tot);
}
}
window.customElements.define('card-lab', cardLab);
-
setSize: To set the total width of cards.
-
stripPx: To remove the 'px' from a width value.
-
mount: To set the web component.
Step 3: Add functions for arrows
class cardLab extends HTMLElement {
//Step 1
//Step 2
next(){
var nextA = this.shadowRoot.querySelector('.next');
var prevA = this.shadowRoot.querySelector('.prev');
var cards = this.children;
var wc = this.stripPx(cards[0].style.width);
var m = this.stripPx(cards[0].style.marginRight);
var vis = this.shadowRoot.querySelector('.external').offsetWidth;
var wt = this.shadowRoot.querySelector('.inner').offsetWidth;
var wPerC = wt / cards.length;
var scroll = this.shadowRoot.querySelector('.main').scrollLeft;
for (var i = 0; i < cards.length; i++) {
var leftC = i * wPerC;
var cal = leftC - scroll + wc + m;
if(cal > vis){
this.shadowRoot.querySelector('.main').scrollTo({
top: 0,
left: leftC,
behavior: 'smooth'
});
break;
}
}
}
prev(){
var nextA = this.shadowRoot.querySelector('.next');
var prevA = this.shadowRoot.querySelector('.prev');
var cards = this.children;
var vis = this.shadowRoot.querySelector('.external').offsetWidth;
var wt = this.shadowRoot.querySelector('.inner').offsetWidth;
var wPerC = wt / cards.length;
var scroll = this.shadowRoot.querySelector('.main').scrollLeft;
for (var i = 0; i < cards.length; i++) {
var leftC = i * wPerC;
var cal = leftC - scroll;
if(cal < 0){
if((cal * (-1)) < vis){
this.shadowRoot.querySelector('.main').scrollTo({
top: 0,
left: leftC,
behavior: 'smooth'
});
break;
}
}
}
}
}
window.customElements.define('card-lab', cardLab);
Step 4: Add a function connectedCallback
It fires when the component is created
class cardLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
connectedCallback () {
//Here will be the functionality.
}
}
window.customElements.define('card-lab', cardLab);
Inside connectedCallback function we will create two things:
- shadowRoot: Is the shadowDOM of the component. It contains the styles of the component and its html definition.
- Conditions to apply certain functions to the component.
Filling the connectedCallback function:
class cardLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
connectedCallback () {
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
.external{
width:100%;
overflow:hidden;
}
.main{
overflow:auto;
overflow-y:hidden;
}
.inner{
padding-top: 10px;
padding-left: 6px;
}
.prev{
width: 60px;
height: 60px;
border-radius: 100px;
position: relative;
float: left;
left: -26px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
background-color:white;
opacity: 0.9;
}
.next{
width: 60px;
height: 60px;
border-radius: 100px;
position: relative;
top: -77%;
float: right;
left: 26px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
background-color:white;
opacity: 0.9;
}
</style>
<div class='external' >
<div class='main'>
<div class='inner'>
<slot name='card'></slot>
</div>
</div>
<a class='prev'>
<i style="position: relative;left: 34px;top: 19px;transform: rotate(135deg);-webkit-transform: rotate(135deg);border: solid black;border-width: 0 3px 3px 0;display: inline-block;padding: 3px;"></i>
</a>
<a class='next'>
<i style="position: relative;left: 16px;top: 19px;transform: rotate(-45deg); -webkit-transform:rotate(-45deg);border: solid black;border-width: 0 3px 3px 0;display: inline-block;padding: 3px;"></i>
</a>
</div>`;
//right arrow event
shadowRoot.querySelector('.next').addEventListener('click', e => {
this.next();
});
//left arrow event
shadowRoot.querySelector('.prev').addEventListener('click', e => {
this.prev();
});
//calling to mount
this.mount();
}
}
window.customElements.define('card-lab', cardLab);
Check a working demo for this code: