Created
March 25, 2021 15:34
-
-
Save esaramago/92417453da582b9a71de05ffdffcf644 to your computer and use it in GitHub Desktop.
Game Timeline 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<game-timeline | |
parts='[ | |
{ | |
"time": 45, | |
"extraTime": 4 | |
}, | |
{ | |
"time": 45, | |
"extraTime": 9 | |
}, | |
{ | |
"time": 15, | |
"extraTime": 2 | |
}, | |
{ | |
"time": 15, | |
"extraTime": 2 | |
} | |
]' | |
events-home='[ | |
{ | |
"time": 2, | |
"extraTime": 0, | |
"icon": "A", | |
"description": "Golo marcado" | |
}, | |
{ | |
"time": 45, | |
"extraTime": 4, | |
"icon": "B", | |
"description": "Golo marcado" | |
}, | |
{ | |
"time": 66, | |
"extraTime": 0, | |
"icon": "C", | |
"description": "Golo marcado" | |
}, | |
{ | |
"time": 120, | |
"extraTime": 2, | |
"icon": "D", | |
"description": "Golo marcado" | |
} | |
]' | |
events-away='[ | |
{ | |
"time": 99, | |
"extraTime": 0, | |
"icon": "A", | |
"description": "Golo marcado" | |
} | |
]' | |
time="90" | |
extra-time="2"> | |
</game-timeline> | |
<!-- | |
--> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GameTimeline extends HTMLElement { | |
constructor() { | |
super(); | |
// element created | |
this.classList.add("c-game-timeline"); | |
this.sliderEl = undefined; | |
this.timeEl = undefined; | |
this.fullTime = null; | |
} | |
render() { | |
const sliderValue = this.calculateSliderValue(this.parts, this.time, this.extraTime); | |
this.fullTime = this.calculateFullTime(this.parts); | |
this.innerHTML = ` | |
<ul class="c-game-timeline__events"> | |
${this.renderEvents(this.eventsHome)} | |
</ul> | |
<div class="c-game-timeline__line"> | |
<input class="c-game-timeline__input js-game-timeline-input" type="range" min="0" max="${this.fullTime}" value="${sliderValue}"> | |
<div class="c-game-timeline__track"> | |
${this.renderTicks(this.parts)} | |
</div> | |
<span class="c-game-timeline__label js-game-timeline-time" style="left: ${this.calculateLabelPosition(sliderValue, this.fullTime)}"> | |
<span class="is-visually-hidden">Tempo de jogo:</span> | |
<span>${this.setLabelText(sliderValue)}</span> | |
</span> | |
</div> | |
<ul class="c-game-timeline__events"> | |
${this.renderEvents(this.eventsAway)} | |
</ul> | |
`; | |
// set elements | |
this.sliderEl = this.querySelector(".js-game-timeline-input"); | |
this.timeEl = this.querySelector(".js-game-timeline-time"); | |
// set events | |
const that = this; | |
this.sliderEl.addEventListener("input", this.onChangeCurrentTime.bind(that, this)); | |
this.sliderEl.addEventListener("change", this.onChangeCurrentTime.bind(that, this)); | |
} | |
// Events | |
onChangeCurrentTime(that, event) { | |
const time = this.calculateCurrentTime(this.parts, event.target.value); | |
this.time = time.time; | |
this.extraTime = time.extraTime; | |
this.updateLabel(); | |
} | |
// Methods | |
calculateSliderValue(parts, time, extraTime) { | |
var gameTime = 0; | |
var totalExtraTime = 0; | |
for (let part in parts) { | |
const partTime = parts[part].time; | |
const partExtraTime = parts[part].extraTime; | |
gameTime += partTime; | |
if (time <= (gameTime + totalExtraTime)) { | |
// return (event time) + (total extra times passed) + (event extra time) | |
return time + totalExtraTime + extraTime; | |
} | |
else if (time <= (gameTime + totalExtraTime + partExtraTime)) { | |
// return (event time) + (total extra times passed) + (current part extra time) + (event extra time) | |
return time + totalExtraTime + partExtraTime + extraTime; | |
} | |
totalExtraTime += partExtraTime; | |
} | |
} | |
calculateCurrentTime(parts, sliderValue) { | |
var gameTime = 0; | |
var totalExtraTime = 0; | |
for (let part in parts) { | |
const time = parts[part].time; | |
const extraTime = parts[part].extraTime; | |
gameTime += time; | |
if (sliderValue <= (gameTime + totalExtraTime)) { | |
return { | |
time: sliderValue - totalExtraTime, | |
extraTime: 0 | |
}; | |
} | |
else if (sliderValue <= (gameTime + totalExtraTime + extraTime)) { | |
const partExtraTime = sliderValue - totalExtraTime - gameTime; | |
return { | |
time: gameTime, | |
extraTime: partExtraTime | |
}; | |
} | |
totalExtraTime += extraTime; | |
} | |
} | |
calculateFullTime(parts) { | |
var fullTime = 0; | |
parts.forEach(part => { | |
fullTime = fullTime + part.time + part.extraTime; | |
}); | |
return fullTime; | |
} | |
updateTime(newValue) { | |
if(this.sliderEl) { | |
this.sliderEl.value = this.calculateCurrentTime(this.parts, newValue); | |
} | |
this.updateLabel(); | |
} | |
updateLabel() { | |
if(this.timeEl) { | |
this.timeEl.textContent = this.setLabelText(this.sliderEl.value); | |
this.timeEl.style.left = this.calculateLabelPosition(this.sliderEl.value, this.fullTime); | |
} | |
} | |
calculateLabelPosition(currentTime, fullTime) { | |
return (currentTime * 100) / fullTime + "%"; | |
} | |
setLabelText(sliderValue) { | |
const time = this.calculateCurrentTime(this.parts, sliderValue); | |
if(time.extraTime) { | |
return `${time.time}+${time.extraTime}'`; | |
} | |
else { | |
return `${time.time}'`; | |
} | |
} | |
renderTicks(parts) { | |
var timePassed = 0; | |
var html = ""; | |
parts.forEach((part, index) => { | |
const time = part.time; | |
const extraTime = part.extraTime; | |
const isLastPart = index === this.parts.length-1; | |
const width = this.calculateExtraTimeWidth(extraTime); | |
if(isLastPart) { | |
html += `<span class="c-game-timeline__extra-time" style="right: 0%; width: ${width}"></span>`; | |
} | |
else { | |
const left = this.calculateExtraTimePosition(time, timePassed); | |
html += `<span class="c-game-timeline__extra-time" style="left: ${left}; width: ${width}"></span>`; | |
} | |
timePassed = timePassed + time + extraTime; | |
}); | |
return html; | |
} | |
calculateExtraTimeWidth(extraTime) { | |
if(!extraTime) return 0; | |
return (extraTime * 100) / this.fullTime + "%"; | |
} | |
calculateExtraTimePosition(time, timePassed) { | |
if(!time) return 0; | |
return ((time + timePassed) * 100) / this.fullTime + "%"; | |
} | |
renderEvents(events) { | |
if(!events) return ''; | |
var html = ''; | |
events.forEach(event => { | |
const minute = `${event.time} ${(event.extraTime ? '+' + event.extraTime : '')}'`; | |
html += ` | |
<li data-role="tooltip" title="<div class=u-font-semibold>${minute}</div> ${event.description}" style="left: ${this.calculateEventPosition(event)}"> | |
<span>${event.icon}</span> | |
</li>` | |
}); | |
/* | |
<svg class="c-icon--xs u-align-middle"> | |
<use xlink:href="Content/images/sprite.svg#${event.icon}"></use> | |
</svg> | |
*/ | |
return html; | |
} | |
calculateEventPosition(event) { | |
const eventTime = this.calculateSliderValue(this.parts, event.time, event.extraTime); | |
return (eventTime * 100) / this.fullTime + "%"; | |
} | |
parseArray(attribute) { | |
const string = this.getAttribute(attribute); | |
if(string) { | |
let array = JSON.parse(string); | |
// fix time and extra time | |
array.time = Number(array.time) || 0; | |
array.extraTime = Number(array.extraTime) || 0; | |
return array; | |
} | |
} | |
connectedCallback() { | |
this.render(); | |
} | |
static get observedAttributes() { | |
return ["current-time"]; | |
} | |
attributeChangedCallback(name, oldValue, newValue) { | |
if(name === "time") { | |
this.updateTime(newValue, this.extraTime); | |
} else if (name === "extraTime") { | |
this.updateTime(this.time, newValue); | |
} | |
} | |
get parts() { | |
return this.parseArray("parts"); | |
} | |
get time() { | |
return Number(this.getAttribute("time")) || 0; | |
} | |
get extraTime() { | |
return Number(this.getAttribute("extra-time")) || 0; | |
} | |
get eventsHome() { | |
return this.parseArray("events-home"); | |
} | |
get eventsAway() { | |
return this.parseArray("events-away"); | |
} | |
set time(newValue) { | |
this.setAttribute("time", newValue); | |
} | |
set extraTime(newValue) { | |
this.setAttribute("extraTime", newValue); | |
} | |
} | |
customElements.define("game-timeline", GameTimeline); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--color-main-500: #c40002; | |
--color-grey-400: #CCC; | |
--color-grey-500: #DDD; | |
--color-grey-800: #AAA; | |
} | |
html { | |
font-size: 62.5%; | |
} | |
body { | |
font-size: 1.4rem; | |
font-family: "Segoe UI", serif; | |
} | |
.is-visually-hidden { | |
opacity: 0; | |
position: absolute; | |
} | |
@mixin gameTimelineHandler { // handler | |
-webkit-appearance: none; | |
position: relative; | |
height: calc(var(--handler-size) * 2); | |
width: var(--handler-size); | |
border-radius: 4px; | |
background: var(--color-main-500); | |
box-shadow: | |
0 0 4px 1px rgb(0 0 0 / 30%), | |
inset 0 0 2px 2px rgb(255 255 255 / 40%); | |
cursor: e-resize; | |
} | |
@mixin gameTimelineMinuteMark { | |
content: ""; | |
position: absolute; | |
right: 0; | |
display: block; | |
width: .2rem; | |
height: calc(var(--track-height) + .4rem); | |
margin: -.2rem auto 0; | |
background-color: var(--color-main-500); | |
} | |
.c-game-timeline { | |
--track-height: .8rem; | |
--handler-size: 1.2rem; | |
position: relative; | |
display: block; | |
max-width: 100rem; | |
padding: 2.4rem 0; | |
margin: auto; | |
} | |
.c-game-timeline__line { | |
position: relative; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
height: 3.2rem; | |
margin: 0 calc(var(--handler-size) * 0.5); | |
} | |
.c-game-timeline__input { | |
-webkit-appearance: none; | |
position: absolute; | |
z-index: 1; | |
width: calc(100% + var(--handler-size)); | |
height: 2rem; | |
background: transparent; | |
outline: 0; | |
&::-webkit-slider-thumb { | |
@include gameTimelineHandler; | |
} | |
&::-moz-range-thumb { | |
@include gameTimelineHandler; | |
} | |
&::-ms-thumb { | |
@include gameTimelineHandler; | |
position: relative; | |
margin-top: 0; | |
z-index: 2; | |
} | |
} | |
.c-game-timeline__track { | |
position: relative; | |
width: 100%; | |
height: var(--track-height); | |
background: var(--color-grey-500); | |
// first minute tick | |
&::before { | |
@include gameTimelineMinuteMark; | |
left: 0; | |
right: auto; | |
} | |
} | |
.c-game-timeline__extra-time { | |
position: absolute; | |
height: var(--track-height); | |
background-color: var(--color-grey-800); | |
&::after { | |
@include gameTimelineMinuteMark; | |
} | |
} | |
.c-game-timeline__label { | |
position: absolute; | |
bottom: calc(100% + 1rem); | |
display: flex; | |
justify-content: center; | |
width: var(--handler-size); | |
margin-left: calc(var(--handler-size) * -0.5 + .2rem); | |
font-weight: bold; | |
text-align: center; | |
white-space: nowrap; | |
} | |
.c-game-timeline__events { | |
position: relative; | |
margin: 0 calc(var(--handler-size) * 0.5); | |
> li { | |
position: absolute; | |
display: flex; | |
justify-content: center; | |
width: 0; | |
line-height: 0; | |
cursor: default; | |
&::before { | |
content: ""; | |
position: absolute; | |
bottom: -1.2rem; | |
left: 50%; | |
width: 1px; | |
height: .8rem; | |
background: var(--color-grey-400); | |
} | |
} | |
~ .c-game-timeline__events { | |
> li { | |
bottom: 0; | |
&::before { | |
top: -1.2rem; | |
bottom: auto; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Game Timeline