Las extensiones de browser son muy faciles de hacer y superutiles para todo tipo de cosas.
Hasta ahora estabamos limitados a usarlas en desktop, pero por fin vamos a poder usar usar extensiones Chrome en android gracias a Kiwi browser, un navegador basado en Chromium.
Ademas, es compatible con remote devices con lo que se pueden combinar las extensiones alojadas en el movil y el control desde el pc, lo que, procurando no exagerar mucho, es el fin del mundo frontend tal y como lo conocemos y el advinimiento de un universo nuevo.
https://www.genbeta.com/paso-a-paso/como-instalar-extensiones-chrome-android
Un puñadico de links
Tools
https://iconsvg.xyz
Otra coleccion de iconos en svg, lo interesante es que se puede personalizar el trazo y otros factores, y te pone el codigo svg resultante facilitando mucho ponerlo inline.
https://codepen.io/sosuke/pen/Pjoqqp
Este es muy bueno y util, una calculadora de colores con filtros css, le indicas un color y te devuelve los css filter necesarios para conseguirlo a partir de una imagen (o elemento) negro
https://9elements.github.io/fancy-border-radius/
Un editor de border radius de 8 puntos, esta caracteristica no es muy conocida y tiene su juego
Learn
Un sitio donde se propone una imagen y hay que replicarla con la menor cantidad posible de css y html. Cuando digo “menor” es que hasta el ultimo bit cuenta.
https://parametric.press/issue-01/unraveling-the-jpeg/
Muy curioso, explica como funciona la compresion jpg y permite alterar las tripas de un archivo .jpg para ver el resultado en la imagen de forma instantanea
https://medium.com/s/story/technical-debt-is-like-tetris-168f64d8b700
Una explicacion muy interesante de la deuda tecnica a partir del juego tetris. Y como en el tetris, la partida contra la deuda técnica no acaba nunca
UX/UI
https://refactoringui.com/previews/building-your-color-palette/
Es muy facil elegir una paleta de colores chula e igual de facil cagarla aplicandola. Consejos practicos sobre como hacerlo:
Autosave en JS para formularios
Ya sabeis, estais rellenando un formulario en una de esas webs de la administración con una pegatina que indica que es compatible con Netscape y se ve mejor con Windows XP, le dais a enviar, o se cuelga, o se pasa cualquier cosa y se pierden todos los datos introducidos. Eso no nos puede pasar a nosotros
Vamos a hacer un script para guardar todos los datos introducidos ante cualquier eventualidad. Para marcar que campos queremos almacenar, añadiremos el atributo “data-memo” a cada elemento del formulario
Empezamos linkando un evento “onbeforeunload” que se ejecutará cuando la pagina vaya a descargarse, por navegar a otro sitio o cerrar el navegador.
window.addEventListener('beforeunload', function() {
save()
});
Definimos la funcion save, creamos un objeto donde guardar la informacion, y hacemos un query por una parte a los campos que no sean checkbox ni radio (o sea, los select, text, etc. de los que guardaremos su id y valor en el objeto memo.fields) y por otro lado, los input radio y checkbox (de los que guardaremos los id de los que estén marcados en un simple array memo.clicks ). Despues, almacenamos todos los datos en un local storage
function save() {
var memo = {fields: {},clicks: []};
document.querySelectorAll("[data-memo]:not([type='checkbox']):not([type='radio'])").forEach(function(item) {
memo.fields[item.id] = item.value;
});
document.querySelectorAll("[data-memo][type='checkbox']:checked,[data-memo][type='radio']:checked").forEach(function(item) {
memo.clicks.push(item.id)
});
localStorage.setItem("memo", JSON.stringify(memo));
}
Si todo va bien, en la pestaña “aplicación” del inspector de Chrome veremos el objeto “stringficado”.
Ahora haremos la función load() para cargar los datos, linkandola a un evento “DOMContentLoaded” del document para que se ejecute lo antes posible.
document.addEventListener("DOMContentLoaded", function() {
load()
});
En esta función leeremos y parsearemos el objeto memo y volcamos sus contenidos en los ids correspondientes leídos en el objeto memo.fields, y cambiamos el checked de los elementos citados en el array memo.clicks.
Vamos a aprovechar para utilizar esta misma función (que setea los elementos) para la opción de borrarlos. De esta forma, si le pasamos un parámetro reseteara el elemento, si no hay parámetro, pondrá el valor almacenado en memo. Así, si nos interesa, podemos poner un botón “limpiar” con una llamada la función “load(1)”. En realidad, podemos poner cualquier parametro, lo importante es que exista.
Discriminamos si existe “f” o no con una expresion ternaria.
function load(f) {
var memo = JSON.parse(localStorage.getItem("memo"));
for (x in memo.fields) {
document.getElementById(x).value = (f ? "" : memo.fields[x])
}
for (x in memo.clicks) {
document.getElementById(memo.clicks[x]).checked = f ? false : true;
}
}
function clean() {
load(1)
}
Podemos evitar el uso del atributo data-memo (para hacer por ejemplo un snippet que nos valga para cualquier formulario ajeno, o una extension de chrome), sustituyendo el “queryselectorall” por algo así:
("form input:not([type='checkbox']):not([type='radio']),
form select:not([type='checkbox']):not([type='radio']) ")
Si algun campo depende de otro, como un select de “localidad” despues del select de “provincia”, podemos usar un settimeout o un evento change asociado al primer select después del primer seteo. Es posible que tengamos que hacer .change() al primer select para activar el evento que rellene el segundo.
Lo encapsulamos todo dentro de un objeto memo_form al estilo del patron de diseño “module” y está vestida para salir de casa.
Podeis juguetear con el tema cambiando valores y refrescando en el siguiente codepen que he preparado:
See the Pen
autosave JS by jorgeblancodeveloper (@jorgeblancodeveloper)
on CodePen.
Haciendo un juego “escape room” solo con CSS, sin JS
A la hora de animar elementos, siempre es mejor hacerlo desde css. En los navegadores, el motor de render que interpreta el css es independiente y mas “barato” en terminos de procesamiento que el interprete de js.
Además, bajo ciertas circunstancias, podremos hacer uso de la GPU del dispositivo (animando solo las propiedades transform y opacity, que se ejecutan después del pintado de la pagina) y ahorrar aun más capacidad de proceso.
Por tanto, siempre será mejor animar con css y usar js solo para asignar las clases que arrancan las animaciones, en vez de usar cosas como las funciones de animación de Jquery (.anim(), .fadeIn() , etc.)
Como demo de lo que se puede conseguir en términos de UX/UI usando únicamente CSS (cero JS) vamos a hacer un juego tipo “escape room”. La “magia” del tema esta en el selector ~ , que significa “hermano menor”, es decir, aquellos elementos que están después, y al mismo nivel, que un elemento determinado, sin ese selector seria imposible nada de lo que vamos a ver.
Y checkbox’s a los que podemos cambiar el estado desde un label (o varios) en cualquier punto del árbol DOM. Con esto, podemos hacer pseudocondicionales y ejecutar “acciones” (cambio de propiedades css) en función de ellos.
Esto es muy práctico para hacer menús acordeón y desplegables que no necesitan JS para funcionar perfectamente, y funcionan desde el primer pintado de la pagina, sin necesidad de que cargue y arranque ningún script, evitando los típicos bloqueos de menús que los ves pero tardan en funcionar en conexiones malas.
El juego
Será un “escape room” 3D en el que podremos mirar a distintos sitios e interactuar con los objetos, tendrá el siguiente mecanismo de juego:
- Cogemos el martillo
- Con el martillo rompemos el cuadro
- Al apagar la luz, en el hueco del cuadro veremos la combinación de la caja fuerte
- Si introducimos una contraseña erronea en la caja fuerte, perdemos la partida, al introducir la correcta, nos da una llave
- Usamos la llave en la puerta y ganamos el juego
El argumento no es como para ganar ningún oscar pero nos sirve para mostrar las posibilidades de css y aprender algunos trucos. El juego será en 3D como hemos dicho, podemos pulsar sobre las paredes para cambiar el punto de vista y hacer zoom sobre los objetos interactivos pulsando sobre ellos.
Layout
El entorno de la habitación vamos a hacerlo en 3D, la contendremos en un div .screen que hará de visor, dentro pondremos un div .room que alojará todos los elementos de la habitación, paredes, objetos, etc.
Para hacer el responsive lo resolvemos con una técnica que uso mucho para escalar textos y otros elementos en proporción directa al tamaño de la pantalla, usando em’s vinculados a un tamaño matriz vw.
Si usáramos pixels para situar y escalar los elementos, solo podríamos ajustar el juego al tamaño de la pantalla usando un transform: scale manejado por JS, de esta forma podemos escalar la ventana de juego a cualquier tamaño cambiando su font-size, bien sea relativo con vw o absoluto en pixels.
Siguiendo esta tecnica de establecer un font-size raiz y usar em’s para escalar sus hijos, aplicamos un font-size al .room de 0.18vw (0.18% del ancho de la ventana).
Esa medida nos permite usar 500em (un numero redondo y bonito) como ancho del .screen ocupando el 90% del viewport (500*0.18=90).
Para conseguir que se aplique una vista 3d tenemos que aplicar una perspectiva al .screen, haciendo:
perspective-origin: center; perspective: 370em;
Cuanto menor sea perspective, mas “angular” será la vista, cuanto mayor, mas “teleobjetivo”.
Con perspective-origin situamos al espectador en el espacio. En este caso lo centramos, pero podemos simular una vision cenital, desplazarlo a un lado, etc.
Tendremos 4 divs que haran las funciones de paredes y suelo, y los colocamos en su lugar usando transformaciones 3d. Hay que tener en cuenta que se aplican según el eje local de los objetos, por lo que el orden en que las aplicamos es muy importante. No acabas en el mismo sitio si te giras a la derecha y andas hacia atrás, que si andas hacia atrás y luego giras a la derecha. Asi, a la pared de la izquierda la giramos 90º en el eje y la desplazamos hacia atrás, y la de la derecha lo mismo pero girándola -90º, ademas de “subirlas” un poco (eje Y) para lograr la perspectiva que nos interesa.
.pared_left{ transform: rotatey(90deg) translatez(-200em) translatey(-100em); } .pared_right { transform: rotatey(-90deg) translatez(-400em) translatey(-100em); }
Además a la habitación, .room, tenemos que aplicar transform-style: preserve-3d; para que no “aplaste” las transformaciones y mantenga el aspecto tridimensional. La habitación mide 600em, y la ventana 500em, por lo que deberemos desplazarla 50em a la izquierda en el eje x para centrarla.
Vamos a hacer todo lo posible con css, por lo que haremos un uso intensivo de :before y :after para añadir elementos sin incorporar gráficos, en este caso los usamos para el ventanuco de la puerta y el agujero de la cerradura. Para el ventanuca nos basta un :before, para la cerradura usamos ademas un after para simular el agujero con un circulo y un cuadrado:
.cerradura:after,.cerradura:before { content: ""; background: #000; } .cerradura:before { width: 6em; height: 20em; left: 12em; top: 7em; } .cerradura:after { width: 15em; height: 15em; border-radius: 15em; left: 8em; top: 5em }
Para hacer los azulejos del suelo, usamos una imagen de 2×2 pixels que convertiremos en base64 para poder incluirla en el css usando cualquier conversor online. Podemos conseguir que los bordes se mantengan nitidos al escalar y así simular una textura de cualquier resolución:
image-rendering: crisp-edges; image-rendering: pixelated;
Vistas
Toda la interacción la haremos con elementos <input>, que serán marcados al pulsar los <label> distribuidos por la habitación. Con ellos podremos alterar cualquier elemento usando nuestro gran amigo del alma, el selector siblings ( ~ ), del que hemos hablado al principio. Con el podremos alterar cualquier elemento de la habitacion haciendo:
#input:checked ~ room .elementoacambiar {propiedades...}
Recordemos que solo permite referenciar a sus hermanos “menores”, por lo que si queremos que un input dependa de otro, debemos de situarlo despues en el html, aunque no tienen porque ser consecutivos.
En el caso de los puntos de vista, como no podemos mirar dos puntos al mismo tiempo, usamos elementos radio de forma que al seleccionar uno se deseleccionan los demás. Como no podemos mover “la cámara” lo que haremos sera mover la habitación. Pondremos un <label> en cada pared vinculado a cada input-radio para poder mover el .room al marcarlos. Para identificarlos facilmente, cada label tendra el mismo nombre que su radio, pero usando clases en los label e id´s en los input. Asi, cuando esté seleccionado el input-radio de la izquierda, rotaremos la habitacion a la derecha para que el espectador mire a la pared correspondiente.
/*---- interacción ---*/ #right:checked ~.screen .room { transform: translatex(-50em) translatez(-00em) rotatey(45deg); } #left:checked ~.screen .room { transform: translatex(-50em) translatez(-00em) rotatey(-45deg) ; }
Ojo, ponemos los labels al 100% de ancho y alto para que cubran toda la habitación, en chrome funcione perfecto, pero algún bug de firefox provoca que se solapen los divs con los label, aunque visualmente no lo hagan, y no se puedan pulsar. Para evitarlo, desactivamos la capacidad de los divs de ser visibles para el al ratón y se la activamos explicitamente a los label para que no la hereden.
.screen div{ pointer-events:none; } label{ pointer-events:all; }
Si juntamos todo lo visto, nos queda algo así, posteriormente pondremos display:none a los <input> para esconderlos al usuario, de momento los dejamos visibles para facilitar la comprensión del sistema. Ya podemos mirar a los lados y al frente.
Los objetos
Vamos a empezar por el interruptor para apagar la luz. Lo haremos con un simple label asociado a un checkbox, como con las vistas, aunque no es necesario en este caso usar radios’s ya que su estado es independiente de que se pulse cualquier otra cosa.
Pondremos un input #luz antes de .room y un interruptor .luz dentro de la .pared_front. Le damos estilo para darle forma de interruptor y situarlo en la pared. Cuando pulsemos sobre el haremos dos cosas, pondremos un :before en .screen con un fondo negro semitransparente e invertiremos el degradado del botón para darle aspecto de pulsado.
Usaremos (de nuevo y como siempre en este tutorial) el selector :checked asociado a #luz y ~ para llegar hasta el elemento que queremos modificar. Como le hemos puesto ya width, height y otros parametros a los :before y :after solo tendremos que darle un content y un background al :after para conseguir el velado:
#luz:checked ~.screen .luz:before { background: linear-gradient(to bottom, #ffffff 40%, #cedbe5 60%); } #luz:checked ~.screen:after { content: ""; }
Ahora añadimos un cuadro que al pulsarlo se caerá. Usamos el procedimiento anterior, añadir un input checkbox antes de .room para controlar su estado y un label dentro de .pared_right para situarlo en la pared de la derecha
En realidad, el “objeto” será la tipica sombra que dejan en la pared los cuadros viejos, el cuadro lo pondremos con un :before y será lo que animemos para hacerlo caer al suelo. Para ello añadimos transition: all 1s ease-in-out entre sus propiedades y una clase #cuadro:checked~.screen .cuadro:before para tirarlo al suelo cuando se clicque:
#cuadro:checked ~.screen .cuadro:before { transform: rotatex(90deg) translatey(120em) translatez(-270em); }
Tenemos un problema, cuando volvemos a clicar sobre la sombra de la pared el input#cuadro se deselecciona y el cuadro vuelve a su sitio, por lo que vamos a desactivar el label con pointer-events:none una vez esté pulsado de forma que no pueda volverse a pulsar, además, vamos a hacer que se lea un codigo al apagar la luz. Como ya hemos “gastado” el :before del .cuadro para hacer el marco, usaremos :after y solo lo mostraremos cuando la luz esté apagada #luz:checked ~ #cuadro:checked es una simulación de condicionales con css:
#cuadro:checked~.screen .cuadro:before { transform: rotatex(90deg) translatey(120em) translatez(-270em); } #cuadro:checked~.screen .cuadro { pointer-events: none; } #luz:checked ~ #cuadro:checked ~.screen .cuadro:after{ content:"01101"; font-size: 40px; display: flex; color: #fff; align-items: center; justify-content: center; }
Ya hemos visto como se hacen las condicionales. Ahora vamos a añadir un objeto que podremos coger y usar, un martillo. Ademas de su input para comprobar el estado, y su label para ponerlo en la pared, tenemos que añadir un elemento de interfaz para mostrar si lo hemos cogido, usaremos un input text (en seguida veremos porque un “text”) que pondremos dentro de .screen.
Inciso: ¿por que sacamos todos los input fuera de .screen menos este?
Primero: para cambiar el estado de cualquier elemento a partir de un input, es mucho mas comodo si tenemos un elemento padre que los contenga a todos y usar la formula input:cheked ~ .container .elemento que tener que tener una ruta especifica para cada uno a partir de siblings.
Ademas, necesitamos acceder a .screen en determinadas ocasiones (insertar el velo al apagar la luz, cuando se gana o se pierde, etc.) y no podriamos hacerlo si los input estuvieran dentro.
Volviendo al martillo. Usaremos como fondo un svg convertido a base64, que minimiza el peso del código y es escalable a cualquier tamaño.
El icono de “objeto cogido” lo sacamos visualmente fuera de .screen y lo hacemos visible (y pulsable) una vez esté #martillo seleccionado usando la clase .icon.
Por defecto le ponemos transform: translatey(-7em) para sacarlo de pantalla y animar su aparición,además de eliminar el objeto del escenario con un display:none cuando se coja, algo así.
#hammer:checked ~.screen .icon.hammer{ transform: translatey(0); } #hammer:checked ~.screen .room .hammer { display: none; }
Ahora viene lo gordo. Queremos que al pulsar un elemento con el martillo “activado” ejecutemos determinada accion y que el cursor adopte la forma del objeto que estamos usando, un martillo o (pronto) una llave.
Pero si usamos un checkbox, cuando lo pulsemos para activarlo, permanecerá “encendido” hasta que volvieramos a pulsarlo, queremos que al hacer click sobre cualquier cosa “soltemos” el objeto, pero no podemos deseleccionar un input desde css.
¿Como podemos hacerlo? Con el selector :focus vinculado a un input tipo text. Gracias a :focus podemos detectar que ha sido pulsado un elemento, cuando pulsemos en algun otro punto el foco cambiara y el elemento volverá a su estado inicial. Para empezar, hacemos que al pulsar sobre el martillo, el icono se convierta en un martillo para indicar que lo hemos “cogido” , lo hacemos, como hemos dicho, usando :focus, por eso hemos puesto el input de icono como “text”, porque retiene el foco.
Así convertimos el cursor en un martillo:
.icon.hammer:focus + .room *{ cursor:url('data:image/png;[...]); }
Ahora tenemos que hacer que el cuadro solo se caiga si hemos cogido el martillo primero. Pondremos el label.cuadro por defecto a pointer-events:none y lo activariamos (pointer-events: all) solo cuando esté el foco activo en .icon.hammer con .icon.hammer:focus ~ .room .cuadro{pointer-events: all} pero aquí me encuentro con un problema. Aunque debería funcionar, y de hecho segun mis pruebas funciona en situaciones tipo elemento:focus ~ elemento pero en este caso, parece que el hecho de estar los elementos anidados hace que se pierda el focus antes de detectar el checked y no funciona.
Lo resolvemos así (si alguien tiene una idea mejor estoy ansioso por oirla):
.icon.hammer:not(:focus) ~ .room .cuadro{ pointer-events:none; animation: activa 1s 1; } @keyframes activa{ from {pointer-events:all } to {pointer-events:none} }
En vez de decirle “Cuando martillo este en focus, dejate clicar” le decimos “cuando NO este en focus, NO te dejes clicar”. Con esto lo que hacemos es provocar un cambio de estado cuando se pierda el foco en .icon.hammer que arranca la animación activa. Pointer-events no es animable en el sentido estricto, un paso gradual de un estado a otro, pero lo utilizamos para que permanezca en “all” durante un segundo después de perderse el foco en .icon.hammer y capture el click. Es una forma de aplicar un delay a la aplicación de una propiedad, luego también lo emplearemos para poner una cuenta atrás. Ya tenemos esto:
Ganar o perder
Vamos a añadir una caja fuerte en la pared de la izquierda, si se intenta abrir con una combinación incorrecta perderemos el juego.
El proceso es similar a cuando pusimos el cuadro, pondremos 5 input que serán las ruedas para introducir la combinación y otro para aplicarla. Ademas, haremos que la cámara se centre en ella cuando la pulsemos, por lo que añadimos un <radio> a los que ya habiamos fijado para las vistas y metemos todo en un contenedor <label> asociado a el. Los selectores de combinación se llamaran .pass_1-…-.pass_5 (según la norma que seguimos, estarán asociados a los input #pass_1-…-#pass_5) y podemos referirnos a ellos sin usar una clase nueva con el lector de atributos de css.
Con [class*=x] buscamos los elementos que contengan “x” en sus nombres de clases:
label[class*="pass"] { background: #ccc; width: 30em; height: 100%; margin: 2em; position: relative; width: 15em; height: 35em; border-radius: 20em; overflow: hidden; box-shadow: 0 0 30px #666 inset; }
Usaremos los :before y :after para añadir el texto 0/1 y un circulo que indique cual está seleccionado, animado con:
#pass_1:checked~.screen .pass_1:before, #pass_2:checked~.screen .pass_2:before, #pass_3:checked~.screen .pass_3:before, #pass_4:checked~.screen .pass_4:before, #pass_5:checked~.screen .pass_5:before { top:21em; }
Antes elegimos la combinación 01101, por lo que vamos a hacer que se acabe el juego al marcar otra.
La forma como mostramos el fin de partida es mostrar un mensaje eliminar la posibilidad de interaccion para que no se pueda seguir jugando.
Lo más sencillo es activar la perdida por defecto al pulsar .box_open, y desactivarla (borrando el contenido) si la combinación era correcta, declarandolo después en el CSS.
Ojo al pointer-events:all que hace que la capa :before bloquee la interacción con lo que hay debajo y no permite seguir “jugando”
#box_open:checked~.screen:before { content: "¡ALARMA!"; font-weight:bold; pointer-events:all; ... #pass_1:checked + #pass_2:not(checked) +#pass_3:not(checked) +#pass_4:checked +#pass_5:not(:checked) ~ #box_open:checked ~ .screen:before { content:none; }
Pongamos que ha puesto bien la contraseña, entonces abrimos la puerta y mostramos una llave para aplicarla a la puerta de la misma forma que hicimos con el martillo al cuadro. Para añadir la llave hacemos lo mismo que antes, checkbox, label y text y replicamos los pasos de mostrar su icono cuando haya sido recogida y eliminarla de la escena.
La llave la pondremos dentro de .box, originalmente tapada por .box_door, y se mostrará al abrir la puerta con:
#pass_1:checked + #pass_2:not(checked) +#pass_3:not(checked) +#pass_4:checked +#pass_5:not(:checked) ~ #box_open:checked ~ .screen .box_door{ transform:translatex(-90%); }
Repetimos lo que hemos hemos hecho con el selector :focus y el martillo para hacer que solo pueda pulsar el label de la cerradura si ha “encendido” primero el icono de la llave. Cuando lo consiga, replicaremos lo que hicimos antes para perder la partida, cambiando el mensaje por uno de victoria.
Ojo: Tendremos que añadir !important al content de .screen:before cuando gana, recordemos que lo estamos eliminando antes al poner la contraseña correcta, con mucha especificidad (seis id’s nada menos)
Para redondear el funcionamiento del juego añadimos una vista a la puerta, para dar a entender que hay que hacer “algo” con ella, y bloqueamos los label que están en un objeto con zoom (la puerta y la caja fuerte) a no ser que estemos en su vista, para no pulsar el botón de abrir la caja fuerte cuando intentamos ir a su vista, por ejemplo:
#left:not(:checked) ~.screen .box, #center:not(:checked) ~.screen .door, #door:not(:checked) ~.screen .door label, #box:not(:checked) ~.screen .box label{ pointer-events:none; }
Apagamos los input para que no se vean y ya tenemos nuestro juego funcionando.
Usando nomenclatura para aplicar estilos
Todos sabemos como usar los atributos para aplicar distintos estilos, marcar los links que van a determinado sitio, los que van a abrir una nueva ventana/pestaña, añadir un icono a los links que apunten a un .pdf… (el origen, el alt, el title, el target, los data-, etc.
) Se puede leerTambien podemos usarlos para marcar de manera arbitraria ciertos contenidos sin necesidad de usar clases ni JS. Por ejemplo un widget de wordpress que permita añadir enlaces, pero no marcarlos arbitrariamente, tal vez no tienes acceso al codigo (o ni siquiera sabes php)
Puedes marcar esos links añadiendo un target “dummy” al final de la url, puede bastar un simple # vacío, normalmente será ignorado en la web de destino (a menos que la url necesite un id target) pero nos permite aplicarles estilo con
Pongamos que quieres o necesitas, por el motivo que sea, usar las mismas imagenes tanto en horizontal (desktop) como en vertical (phone), y lo apañas con objet-fit:cover y object-position:center , o bien usando sus equivalentes de background-image y background-position si necesitas dar soporte a Explorer y browsers antiguos, para el caso es lo mismo.
Y te encuentras con el problema de que ciertas imágenes tienen el punto de interés desplazado a un lado y lo pierdes en móvil, o en la parte superior y se pierde en desktop. Resulta que le estas cortando la cabeza a esa modelo tan guapa de la tercera imagen de tu slide.
Podrías arreglarlo aplicándoles una clase especial a esas imágenes lo que te obligaría en la mayoría de los casos a añadir un campo nuevo en tu gestor de contenidos para aplicarla, si es que tienes la posibilidad de hacerlo, que a lo mejor ni siquiera tienes acceso por ser una libreria externa.
Tampoco puedes hacer algo como .slide li:nth-child(3) porque te dejaria de funcionar al borrar o insertar una nueva imagen, o si se aplica un random al orden de slides.
Bueno, pues no hace no hace falta usar clases especificas, puedes señalar esas imágenes utilizando su propio nombre para identificarlas utilizando el lector de atributos de CSS.
Con suerte, tu gestor de contenidos permitira añadirle un “alt text” donde podemos colocar nuestras “falsas clases” y leerlas con [src*= “”] , con este selector podemos identificar cadenas de texto inscritas en el nombre (en la ruta, en la realidad) de la imagen. En concreto, el operador * lo que hace es buscar la cadena citada en cualquier lugar del atributo, también podemos añadir más precisión usando otros operadores como ^, $, |… que buscan la cadena al inicio, al final, como cadena completa, etc. tenéis la lista completa y mas información en el link anterior. Para la mayoría de los casos con * es suficiente.
Por ejemplo en una imagen horizontal con el punto de interés a un lado podemos añadir los sufijos @left o @right (u otros) en el nombre de la imagen y al mostrarla en vertical identificarla en el css y posicionarla en consecuencia:
img { object-position:center center; } img[src*="@right"] { object-position: right center; } img[src*="@left"] { object-position:left center ; }
Una solución limpia y sencilla como deben ser siempre en CSS y que no complican nuestra base de datos. La imagen de la chica que mencionabamos antes ahora acaba en @top y se muestra correctamente en desktop.
Podemos combinar propiedades con variables CSS de esta manera
.screen img{ width:100%; height:100%; object-fit:cover; --originx: center; --originy: center; object-position:var(--originx) var(--originy); } .screen img[src *= "@r"]{ --originx: right; } .screen img[src *= "@l"]{ --originx: left; } .screen img[src *= "@t"]{ --originy: top; } .screen img[src *= "@b"]{ --originy: bottom; }
Así podríamos hacer algo como imagen@l@t.jpg para localizar puntos de interés en las esquinas.
Ejemplito en codepen:
Podemos marcar imagenes “en origen” para aplicar variaciones de composicion en logos con una relacion de apecto demasiado extrema en un directorio de empresas, por ejemplo, o en definitiva cualquier ocasion donde no podamos añadir clases arbitrariamente pero podamos editar el contenido.
No es la solucion mas bonita del mundo pero es un recurso para casos extremos.
Depurando tu CSS con las dev tools de Chrome
En fin, ese css tan hermoso y limpio en origen ha pasado varias guerras, nacimientos y muertes en el DOM y ahora es un empastre inescrutable de miles de lineas donde nadie se atreve a quitar nada.
Pero hay herramientas para ayudarte a ponerlo en vereda.
Analiza tu css y te devuelve un completo informe con la cantidad de colores, fuentes, gráficos de especificidad, etc. Todas las estadísticas sobre tu CSS necesarias para tener un primer vistazo de la situación y un punto de referencia con el que comparar el trabajo una vez terminado.
Ademas puedes cotillear informes de sitios populares.
Interpretar el gráfico de especificidad daria para articulo completo pero baste saber que debería ser lo mas lo mas plano y bajito posible, señal de que no hay id’s, ni !important’s, ni selectores “largos”, etc.
No obstante, la información de fuentes y colores ya es muy útil para ir localizando código repetido e innecesario,
Ya tenemos unas estadísticas y hay que localizar el css no utilizado. Podemos ayudarnos de la función coverage, del inspector de chrome, nos vamos a la ventana (more tools> coverage) y pulsamos el botón de iniciar grabación.
Nos aparecerá los CSS y JS de la pagina web con una barra indicando que porcentaje del archivo es usado ademas de otra información. Podemos pinchar sobre el nombre del fichero css para ver su fuente en la ventana sources, donde se nos marca con barras rojas/verdes las clases no usadas/usadas.
Inicialmente se nos marcan en rojo tanto las clases CSS como las funciones JS que no se utilizan, como algunas dependen de cambios en el DOM, conforme van siendo utilizadas al navegar se van marcando en verde.
Una vez finalizada la navegación, tendremos una primera aproximación de las clases que podemos borrar, podemos hacerlo ahi mismo en la ventana de sources y luego hacer copipaste, o bien usar la funcion overrides para guardar una copia en disco de las ediciones que hacemos.
Para activar overrides nos vamos a su pestaña en la ventana izquierda de sources (es posible que tengamos que pulsar >> para verla) y si es la primera vez que la activamos, nos pedirá un directorio donde almacenar los cambios.
A partir de ahora, podemos hacer los cambios necesarios en el css (como borrar las clases pintadas en rojo por no ser usadas) y al hacer control+s se nos guardará la copia del css en el directorio indicado. Es más, al recargar la pagina, mientras tengamos activado el checkbox de “Enable local overrides” se usara el css guardado en local en vez del css del servidor.
Ojo con esto, no lo dejemos activado sin darnos cuenta al acabar la faena o nos puede confundir. Tenemos alguna ayuda visual para evitarlo, arriba, en el nombre del archivo, nos aparece una bolita morada para indicar que es un archivo sobrescrito en local, y un asterisco si hay cambios sin guardar
También tendremos un ojo puesto en la ventana computed del inspector, ahí nos indicara cuantas clases estamos chafando en cada elemento, cifra que trataremos de mantener al mínimo.
Una vez limpio el css es buena idea es pasar el CSS a SASS, si es que no lo usamos ya, (que seguro que no, porque si no usar estas herramientas no tendría mucho sentido), para ello tenemos varios servicios en linea como http://sebastianpontow.de/css2compass/ que está muy bien y nos permite bastantes opciones.
Incluso si no fuéramos a utilizar SASS (que deberiamos si o si), nos ayudará a comprender que clases dependen de otras y como están anidadas en el CSSDOM.
Ya tenemos el CSS limpio de clases no usadas y ordenado. El siguiente paso seria ajustar su nomenclatura con un poco de sentido y coherencia según alguna de las metodologias que hemos visto.
A mi me gusta marcar las clases que dependen de otras al estilo de BEM, pero me encanta usar la herencia, y uso tags semanticos, por lo que no hago una herencia paralela basada en la nomenclatura como exige el sistema, solo tomo de BEM la nomenclatura de clases.
Por ultimo seria conveniente dividirlo minimamente al estilo de SMACSS en modulos según su función al gusto de cada uno, usualmente yo parto de: reset (inicializacion y normalize) , composicion (layout y maquetacion, margenes..) y aspecto (colores, fondos, fuentes…).
Sistema de templates realizado en Node.js
Este es un sistema sencillo de templates realizado con Node que hice como paso intermedio para adaptar nuestro ecosistema de plantillas a Twig, me permitió ir limpiando código duplicado e identificando bloques de forma rápida. La escribí desde cero y aunque es muy sencilla, funciona realmente bien. Está pensada para trabajar con plantillas php y necesita las siguientes carpetas:
/sources los archivos de texto con la extensión .tpl que generan las templates
/base Las plantillas base de las que derivan las demás.
/mods los módulos php para trabajar con los bloques
Se dividen los bloques de código susceptible de ser modificado con comentarios php con un nombre arbitrario con el siguiente formato (se pueden anidar):
<?php#bloque?> <?php#subbloque?> <?php#/subbloque?> <?php#/bloque?>
Si fuera necesario usarlos como “ancla” para añadir contenido se pueden poner bloques vacíos donde sea necesario sin problema, o añadir bloques en cualquier momento que surja la necesidad.
Primera linea>> el nombre (sin la extensión) de la plantilla base
Siguientes líneas>> modificaciones a la plantilla base.
El motor interpreta linea a linea secuencialmente, modificando el código resultado de aplicar la linea anterior, o sea que si en una línea cargamos un módulo, en las siguientes podemos modificar sus bloques cargando nuevos módulos en ellos y así hasta el infinito (o se acaben las lineas del archivo .tpl, lo primero que ocurra 🙂 ).
Una plantilla podría tener el siguiente contenido:
fr_base //coge la plantilla base/fr_base.php
head > head_orange // cambia el bloque head por el contenido de mods/head_orange.php
main + thumbs // a continuación del bloque “main” añade el modulo mods/thumbs.php
boton – //borra el bloque boton, que puede estar en cualquiera de los bloques anteriores
Toda la magia del sistema está en esta Regexp:
RegExp("<\\?php#" + origen + "\\?>((?!<\\?php#\\/\\?>).|\\r\\n|\\r|\\n)*<\\?php#\\/" + origen + "\\?>");
“Origen” es el bloque indicado a la izquierda del operador (>, + o -), con esto seleccionamos el contenido del bloque y el resto es trivial. Permite mantener los comentarios usuales en los phps porque se limita a buscar comentarios que estén cerrados con “/”
var fs = require('fs');
var glob = require('glob');
compila();
function compila() {
const util = require('util');
const glob = util.promisify(require('glob'));
glob('../cdn.landing.engine/sources/**/*.tpl', function(err, files) {
files.forEach(function(element) {
lee_plantilla(element);
});
});
}
function lee_plantilla(template) {
fs.readFile(template, (err, data, isdone) => {
data = data.toString().replace(" ", "");
if (data.charAt(data.length - 1) != "\n\n") {
data += "\n"
}
if (data.split(/[\r|\n]+/).length > 1) {
var index = 0;
data.split(/[\r|\n]+/).forEach(function(line) {
index++;
switch (index) {
case 1:
codigo = fs.readFileSync("..engine/sources/base/" + line + ".php");
break;
case data.split(/[\r|\n]+/).length:
escribe_php(codigo.toString(), template.split("/").pop())
break;
default:
codigo = interpreta(codigo.toString(), line, template);
break;
}
})
} else {
fs.readFile("../cdn.landing.engine/templates_sources/base/" + data.toString() + ".php", (err, data, isdone) => {
escribe_php(data.toString(), template.split("/").pop())
})
}
})
}
function escribe_php(contenido, destino) {
fs.writeFile("../engine/templates/" + destino.replace(".tpl", ".php"), limpiatags(contenido), function(err) {
console.log("error" + err)
});
}
function interpreta(codigo, linea, template) {
if (linea.indexOf('+') > -1) {
divisor = linea.indexOf('+');
var operador = "+";
}
if (linea.indexOf('>') > -1) {
divisor = linea.indexOf('>');
var operador = ">";
}
if (linea.indexOf('-') > -1) {
divisor = linea.indexOf('-');
var operador = "-";
}
var origen = linea.slice(0, divisor);
var destino = linea.slice(divisor + 1, linea.length);
var formula = new RegExp("<\\?php#" + origen + "\\?>((?!<\\?php#\\/\\?>).|\\r\\n|\\r|\\n)*<\\?php#\\/" + origen + "\\?>");
var a = codigo.match(formula).slice(0, -1);
new_code = fs.readFileSync("../engine/sources/mods/" + destino + ".php");
switch (operador) {
case "+":
return codigo.replace(a, (a + new_code));
break;
case ">":
return codigo.replace(a, new_code);
break;
case "-":
return codigo.replace(a, "");
break;
}
}
function limpiatags(codigo) {
var formula = new RegExp("<\\?php#((?!>).)*\\?>", "g");
var a = codigo.match(formula);
if (a) {
for (var i = 0; i < a.length; i++) {
codigo = codigo.replace(a[i], "")
}
}
return codigo.replace(/^(?:[\t ]*(?:\r?\n|\r))+/gm, "")
}
Carrusel tipo baraja con CSS y JS mínimos
Una prueba que he realizado para un requerimiento del trabajo. Al final no la hemos utilizado, pero me ha quedado mona de forma muy sencilla y pongo el código por aquí por si le sirve a alguien.
Se puede poner cualquier numero y tamaño de li’s, a partir del código es fácil hacer botones para invertir el sentido del carrusel, ir a determinada imagen, etc. pero quería mantener el ejemplo con el mínimo número de lineas, tal y como esta son unas 40 lineas de CSS y 5 de JS.
El proceso es sencillo, animo el ultimo <li> de la “baraja” pasandolo al fondo de forma visual con CSS (alterando su z-index) y una vez terminada la animación (con un simple intervalo de igual o mayor duración que la animación CSS) convierto ese reordenamiento en “físico” moviendo el <li> en el DOM con JQuery, anulando el efecto CSS al mismo tiempo, y empiezo de nuevo el bucle.
Algunos detalles:
• Los ultimos 4 <li> tienen una rotacion escalonada y estan referenciados como :nth-last-child(), con lo que al alterar la posicion en el DOM con JQuery se recolocan.
• Pongo un mínimo de rotación, rotate( 0.1deg ), porque si ponemos rotación cero se desactiva el efecto y se aprecia un saltito raro en el suavizado.
• Los <li> no estan cerrados. Puede parecer raro pero es perfectamente valido en HTML desde el inicio de los tiempos. Además dejarlos abiertos tiene ventajas como que no aparece espacio entre los <li> al justificarlos con inline-block.
Ejemplo de uso de snippets
Los snippets son trozos de código JS que se puede ejecutar sobre cualquier web desde el navegador. Están disponibles tanto en Chrome como en Firefox, abrimos el inspector y en la pestaña de sources, en la ventana izquierda, veremos la sección de snippets, ahí podemos añadir los que queramos. Nos permiten interactuar con una web automatizando procesos. Para muchas cosas son mas cómodos que las extensiones porque los puedes editar al vuelo y son mas sencillos de aplicar.
A veces los uso para automatizar interacciones con formularios de alguna web, como en el siguiente ejemplo:
Lo que cuento es un poco “irregular”, o sea que pongamos que lo ha hecho un amigo :).
Pues mi amigo necesitaba una base de datos de marcas y modelos de teléfonos móviles, pero no conseguía encontrar ninguna.
Al final encontró una web con dos selectores, uno de marcas y otro de modelos que se actualizaba automáticamente al elegir una marca mediante una llamada php.
¿Como extraer la información? Pues con un becario que fuera copiando ambos resultados.
Pero como no había becario a mano tuvo que utilizar un snippet.
Este es el código que usamos, simplemente se pega y haciendo botón derecho > run o desde el boton de la parte inferior de la ventana de codigo lo ejecutamos:
index = 1; //aqui almacenaremos los datos datajson = {}; //contamos las marcas del selector cuantos = $("#selector_marcas").size(); copia(); function copia() { //seleccionamos la primera marca document.getElementById("selector_marcas").selectedIndex = index; //lanzamos un evento change para que se rellene el selector de modelos y ponemos un retardo para dar tiempo a que se rellene... $("#selector_marcas").change(); setTimeout(function() { datajson[$("#selector_marcas").val()] = []; // y lo rellenamos con las opciones del selector de modelos $("#selector_modelos option").each(function() { datajson[$("#selector_marcas"). val()].push($(this).val()) }); //borramos el primer dato del array, que contiene el texto "elige modelo" datajson[$("#selector_marcas"). val()].splice(0, 1); console.log(index + " de " + cuantos); if (index < cuantos) { index++; copia(); } else { //convertimos el objeto en una cadena de texto y la sacamos por consola console.log(JSON.stringify( datajson)) } }, 1000); }
Cuando acaba podemos hacer botón derecho sobre la consola y “copiar” para pegar el json en un archivo de texto.
Trabajando con Node.js
He empezado a profundizar con Node, que hasta ahora no lo usaba más que como un entorno donde correr Gulp y poco más.
Es muy sencillo y usa the-old-good JS de toda la vida sin más problemas ni lios. Las dependencias se inyectan de forma muy sencilla y puedes tener toda una aplicación en un solo archivo si no es necesario más.
De repente ya no quiero tanto a Gulp, me he dado cuenta de que casi todo para lo que usaba Gulp lo puedo hacer usando Node y usando los módulos NPM directamente sin esperar a que alguien los adapten a Gulp o mantengan actualizados.
Por ejemplo para automatizar los pantallazos de testing la única herramienta que he encontrado en Gulp usa PhantomJs, que es un proyecto abandonado y emplea un motor de render webkit con 10 años que ni siquiera acepta Flex. Sin embargo, usando Node directamente, puedo escribir un script que use Puppeteer y renderice con Chrome headless, compare produccion y stagging y me guarde pantallazos con los errores marcados en una carpeta. Tenemos que testar mas de 300 webs, y lo hace tan rápido que llamaron del servicio de hosting creyendo que estaba recibiendo un ataque DDoS (tuve que ralentizarlo)
Conforme más aprendo de Node menos uso Gulp y creo que en breve lo utilizare solo residualmente.
Aunque hasta el momento solo lo estoy usando como motor de script, empiezo a experimentar con servidores y es una verdadera gozada poder hacer un back-end sin salir de javascript, con las herramientas que antes estaban vetadas ejecutando en el cliente, como escribir archivos, manipular bases de datos, etc. cosas para las que hasta ahora necesitaba usar scripts intermedios en php.
Como bonus, es el lenguaje que utilizan las placas Arduino, con lo que si un dia me decido por fin a construir un ejercito de robots para destruir el mundo ya tengo parte del camino hecho.
En fin, que mola mucho.