Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CSS Fútbol

Manz
July 15, 2019

CSS Fútbol

Título: CSS Fútbol
Tema: CSS, PostCSS & WebComponents
Evento: TLP Tenerife 2019 ( https://tlp-tenerife.com/ )
Lugar: Recinto ferial de Santa Cruz de Tenerife (Sala IBM)
--------------------
Descripción: ¿Recuerdas o viviste la época de los primeros «PC-Fútbol»? En un ataque de nostalgia, he preparado una charla en la que explicaré paso a paso como hacer una demo visual de un campo de fútbol para representar alineaciones de un equipo, utilizando tecnologías como HTML5, CSS3, Javascript, PostCSS y WebComponents. Sin frameworks. Sin imágenes (o casi).

GitHub: https://github.com/ManzDev/css-futbol

Manz

July 15, 2019
Tweet

More Decks by Manz

Other Decks in Programming

Transcript

  1. :root { background: black; height: 100%; display: flex; justify-content: center;

    align-items: center; } <h l> eq a n (mo p ifi t ) Ali -it Jus y-co n
  2. .zone { display: flex; height: 100%; align-items: center; & .area

    { width: 166px; height: 250px; border: 4px solid white; border-left: 0; } } .left .zone 166px 250px
  3. .zone { ... & .area { --area-width: 166px; --area-height: 250px;

    width: var(--area-width); height: var(--area-height); ... } } .left .zone calc(var(--field-width)/6); calc(var(--field-height)/2);
  4. .area { ... display: flex; align-items: center; & .smallarea {

    width: 40%; height: 60%; border: 4px solid white; border-left: 0; } } .left .zone 40% 60%
  5. <div class="area"> ::before Texto ::after </div> .area::before { content: "Hola";

    } .area::after { content: "Adiós"; } HolaTextoAdiós
  6. .area { ... position: relative; &::before { content: ""; border:

    2px solid white; border-radius: 50%; width: 4%; height: 3%; position: absolute; left: 67%; } } .left .zone
  7. .area { ... position: relative; &::after { content: ""; border:

    4px solid white; border-radius: 50%; width: 100%; height: 60%; position: absolute; left: 17%; } } .left .zone
  8. .area { ... &::after { ... clip-path: polygon(100% 0, 80%

    0, 80% 100%, 100% 100%); } } .left .zone
  9. .goal { border: 4px solid white; border-right: 0; width: 10%;

    height: 35%; position: absolute; left: -14%; } .left .zone
  10. <div class="left zone"> <div class="top corner"></div> <div class="area"> <div class="goal"></div>

    <div class="smallarea"></div> </div> <div class="bottom corner"></div> </div>
  11. .zone { position: relative; & .corner { border: 4px solid

    white; border-radius: 50%; width: 15%; height: 5%; position: absolute; left: -10%; } } .left .zone
  12. .zone { position: relative; & .corner { ... clip-path: polygon(

    100% 50%, 100% 100%, 50% 100%, 50% 50%); } } .left .zone
  13. .zone { ... & .corner { ... &.top { top:

    -3%; } &.bottom { bottom: -3%; transform: scaleY(-1); } } } .left .zone
  14. <div class="right zone"> <div class="top corner"></div> <div class="area"> <div class="goal"></div>

    <div class="smallarea"></div> </div> <div class="bottom corner"></div> </div> .left .middle .right .zone .zone .zone
  15. <div class="middle zone"> ::before ::after </div> .middle .zone .middle {

    border-left: 4px solid white; width: 0; } ci l s o
  16. .middle .zone .middle::before { content: ""; border: 4px solid white;

    border-radius: 50%; width: calc(var(--field-width)/0.15); height: calc(var(--field-width)/0.15); position: absolute; transform: translateX(-51%); }
  17. .middle .zone .middle::after { content: ""; border: 2px solid white;

    border-radius: 50%; width: 8px; height: 8px; position: absolute; transform: translateX(-65%); } id y, --s o -wi h
  18. .field.mosaic { background-image: repeating-linear-gradient(to bottom, #0002 0 80px, transparent 80px

    140px), repeating-linear-gradient(to left, #044d04 0 80px, #066606 80px 140px); }
  19. <div class="field"> ... <div class="playable"></div> </div> .playable .field { position:

    relative; & .playable { position: absolute; width: 100%; height: 100%; } }
  20. <div class="playable"> <div class="player p1" data-number="1"></div> <div class="player p2" data-number="2"></div>

    ... <div class="player p11" data-number="11"></div> </div> .player.p$[data-number="$"]*11
  21. .player .player { --player-width: calc(var(--field-width)/15); width: var(--player-width); height: var(--player-width); background:

    grey; border: 4px solid white; border-radius: 50%; box-shadow: 4px 4px 14px rgba(0, 0, 0, 0.5) inset, 4px 4px 10px rgba(0, 0, 0, 0.5); }
  22. .numbers .player::before { content: attr(data-number); font-family: Montserrat, sans-serif; font-weight: 700;

    font-size: calc(var(--player-width) / 2); display: flex; justify-content: center; } .player 2 <div class="playable numbers"> ... </div>
  23. .tenerife .player { background: #000 linear-gradient( #fff 60%, #00f 60%,

    #00f5 90%); } .barça .player { background: linear-gradient(to bottom, transparent 0% 60%, #0a2455 60%), repeating-linear-gradient(to right, #1d3b71 0% 20%, #8f1d2a 20% 40%); &::before { color: yellow; } } 2 <div class="playable numbers tenerife"></div> #fff #00f #00f5
  24. .realmadrid .player { background: #fff linear-gradient(to bottom, #fff 60%, #000

    60% 61%, #fff 64%); } .spain .player { background: linear-gradient(to bottom, transparent 0% 60%, #035781 60%), linear-gradient(to right, #f31f2d 5% 10%, #eebe4f 10% 16%, #84485f 16% 22%, #f31f2d 22% 40%); &::before { color: yellow; } } 2 <div class="playable numbers spain"></div>
  25. .atleti .player { background: linear-gradient(to bottom, transparent 0% 60%, #202669

    60%), linear-gradient(to right, #a40f23 0% 20%, #fff 20% 40%); &::before { text-shadow: 0 0 4px #fff; } } .player.goalkeeper { background: linear-gradient( #111 60%, #888 60%); &::before { color: #eee; } } <div class="player p1 goalkeeper"></div>
  26. [data-formation] /* Default formation */ .playable { display: flex; justify-content:

    center; align-items: center; & .player { margin: 5px; } }
  27. [preparing-grid] [data-formation] { display: grid; justify-content: space-around; align-items: center; }

    .p1 { grid-area: a; } .p2 { grid-area: b; } ... .p10 { grid-area: j; } .p11 { grid-area: k; }
  28. [data-formation] [data-formation="1-5-5-def"] { grid-template-areas: ". b . g" ". c

    . h" "a d . i" ". e . j" ". f . k"; grid-template-columns: repeat(7, 1fr); }
  29. <div id="app"> <div class="field"> <div class="left zone">...</div> <div class="middle zone"></div>

    <div class="right zone">...</div> <div class="playable">...</div> </div> </div> añadimos un contenedor
  30. .field { --field-rotate-x: 0deg; --field-scale: 1; transform: rotateX(var(--field-rotate-x)) scale(var(--field-scale)); }

    body { overflow: hidden; } #app { perspective: 150px; perspective-origin: bottom; }
  31. const field = { rx: 0 } domfield.addEventListener('wheel', function(event) {

    field.rx += event.deltaY > 0 ? 0.25 : -0.25; domfield.style.setProperty('--field-rotate-x', `${field.rx}deg`); }
  32. field rotateX player rotateX = player translateZ field scale shift

    0 20 field rotateX *-1 --field-scale --field-rotate-x --player-translate-z
  33. .field { ... --player-rotate-x: 0deg; ... } .field, .playable {

    transform-style: preserve-3d; } .player { transform: rotateX(calc(var(--field-rotate-x) * -1)); }
  34. domfield.addEventListener('wheel', function(event) { ... { field.rx += event.deltaY > 0

    ? 0.25 : -0.25; player.z += event.deltaY > 0 ? 0.5 : -0.5; } domfield.style.setProperty( '--player-translate-z', `${player.z}px`); } const player = { z: 0 }
  35. .field { ... transition: transform 0.2s linear; will-change: transform; }

    .player { ... transition: transform 0.25s linear; will-change: transform; }
  36. @media screen and (700px <= width <= 1100px) { #app

    { & .field { --field-width: 700px; --field-rotate-z: -90deg; } } } 700px - 1100px
  37. @media screen and (700px <= width <= 1100px) { #app

    { & .field { ... } & .player { transform: rotateZ(90deg); } } }
  38. .player { transition: all 0.75s; } .field { transition: all

    0.25s; } @media screen and (700px <= width <= 1100px) { ... } } transform will-change: transform;
  39. @media screen and (width <= 700px) { .field { --field-width:

    400px; --field-height: 250px; } } <= 900px
  40. <div class="field"> <div class="left zone"></div> <div class="top corner"></div> <div class="area">

    <div class="goal"></div> <div class="smallarea"></div> </div> <div class="bottom corner"></div> </div> <div class="middle zone"></div> <div class="right zone"> ... </div> </div> <field-area class="left"></field-area> ... <div class="player p5" data-number="5"></div> <div class="player p6" data-number="6"></div> ... <team-player number="6"></team-player>
  41. class TeamPlayer extends HTMLElement { constructor () { super(); ...

    this.innerHTML = 'Contenido'; } } customElements.define('team-player', TeamPlayer); <team-player>Contenido</team-player>
  42. class TeamPlayer extends HTMLElement { constructor () { super(); this.number

    = parseInt(this.getAttribute('number')); this.dataset.number = this.number; this.classList.add('player', `p${this.number}`); ... } } customElements.define('team-player', TeamPlayer); <team-player number="6"></team-player> 6 p o s at b e
  43. class TeamPlayer extends HTMLElement { constructor () { ... this.innerHTML

    = ` <div class="attrs"> <div class="life"> <div class="fill"></div> </div> </div>`; ... } } 6 .attrs .fill .life
  44. .attrs { & .life { width: 100%; height: 10px; background:

    #000; box-shadow: 0 0 10px rgba(0,0,0, 0.5); border-radius: 10px; overflow: hidden; & .fill { background: var(--player-energy-color); height: 100%; transition: width 0.5s ease; } } } .life .fill
  45. .attrs { opacity: 0; transition: opacity 0.5s ease 0.15s; &

    .fill { width: 0; } } .player:hover .attrs { opacity: 1; transition: opacity 0.5s ease 0; & .fill { width: var(--player-energy); } } width: 0; width: var(--player-energy);
  46. class TeamPlayer extends HTMLElement { constructor () { ... this.style.setProperty(

    '--player-energy', `${this.energy}%` ); this.style.setProperty( '--player-energy-color', getEnergyColor(this.energy) ); ... } }
  47. const ENGINE = 5; class Commentator { constructor () {

    speechSynthesis.addEventListener('voiceschanged', () => { this.voiceEngine = speechSynthesis.getVoices()[ENGINE]; }); } speak (text) { const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'en'; utterance.voice = this.voiceEngine || null; speechSynthesis.speak(utterance); } }
  48. class StadiumBillboard extends HTMLElement { constructor () { super(); ...

    this.innerHTML = ` <style> stadium-billboard { border: 2px solid white; } </style> <div class="left panel"></div> <div class="right panel"></div>`; } } <stadium-billboard></stadium-billboard> Glo C & H L (no l "wa " )
  49. class StadiumBillboard extends HTMLElement { constructor () { super(); …

    this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <style> :host { border: 2px solid white; } </style> <div class="left panel"></div> <div class="right panel"></div>`; } } Loc S & H L (s a w "wa ")
  50. stadium-billboard { border: 2px solid white; } Web ne stadium-billboard

    { border: 4px solid red; & .left.panel { border: 4px solid blue; } } Glo C <stadium-billboard></stadium-billboard>