I have a code where the "card" class takes the user to an article when clicked.
And its child, "category", takes the user to another website when clicked.
<div class="card">
<div class="img"></div>
<div class="category"></div>
<div class="title"></div>
<div class="description"></div>
</div>
I'm trying to write CSS animations with div:active for these two divs.
So when I have the following, the whole card animates:
.card:active {
transform: translateX(50px);
transition: all 0.5s ease;
}
But I don't want the card to animate when the user clicks on the category div.
So, I tried something like the following, and others, which didn't work.
:not(.category).card:active{
transform: translateX(50px);
transition: all 0.5s ease;
}
Is there a combination of :not and :active pseudo classes that I could use to make the card animate when clicked, but not animate when the category is being clicked?
solution 1, CSS only
Generally one could use...
/* CSS */
.card { pointer-events: none }
.card>:not(.category) { pointer-events: auto } /* all kids except .category */
.card:active { transform: translateX(50px) }
/* HTML */
<div class="card">
<div class="img">image</div>
<div class="category">CATEGORY</div>
<div class="title">no-card</div>
<div class="description">description</div>
</div>
...and clicking any child of .card, except class .category, will trigger the card :active event as well as :hover. However, any card space not occupied by child elements wil not trigger any event (i.e. .card:padding and .category will not trigger :active or :hover).
Another drawback is that .category will listen to no events at all and therefore cannot be an input element that needs to handle those events (like a <button>, as shown in the demo).
If this is acceptable, this solution is the easiest to code and maintain.
solution 2, CSS plus JS
This solution uses only simple CSS...
.effect:active { transform: translateX(50px) } /* NOT .card:active */
...and some Vanilla Javascript (pseudo code) that simply removes/adds the .effect class from .card when appropriate.
forEach cardList.item do
card.onmouseover = enableEffect();
card.category.onmouseenter = disableEffect();
card.category.onmouseout = enableEffect();
disableEffect = remove class 'effect' from .card
enableEffect = add class 'effect' to .card
The below snippet includes both solutions, is heavily commented and includes a few responsiveness extras (like CSS columns, main font and page spacing. Math used MathIsFun: Linear Equation).
Just copy the code and have fun with it!
SNIPPET
'use-strict';
// Traverse an array and execute the passed callback function for each array element found
var forEachEntryIn = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) { callback.call(scope, i, array[i]); } };
// Get the list of cards
var cards = document.getElementsByClassName('card');
// Make this a function and you can toggle it with a <button>
var DEBUG = false; // set to 'true' for debug view and some console output
(DEBUG) ? document.body.setAttribute('outlines','1') : document.body.setAttribute('outlines','0');
// Traverse the list of cards
forEachEntryIn( cards,
function (idx,card,scope) {
// '.effect' is needed by default,
// so why add it in HTML class="" property when we can do it here...
card.classList.add('effect'); // remove if you want to assign in HMTL anyway
/*
MOUSEOVER events are bubbled to child elements
MOUSEENTER does not bubble, needed on '.category'
target: the element that triggered the event ('.card' OR any of its child elements)
currentTarget: the element that the event listener is attached to: '.card'
*/
card.onmouseover = function(e) { // Attach 'MOUSEOVER' listener to '.card'
// Parent check: event may be bubbled (from any '.card' children)
// So, is the parent a '.card' or maybe its parent?
if (e.target.parentElement == e.currentTarget) {
enableEffect(e.target.parentElement); // Activate '.card' animation
};
// NOTE: Disable the check, click a card and see what happens....funny!
};
var category = card.querySelector('.category');
if (card.contains(category)) {
category.onmouseenter = function(e) { disableEffect(e.currentTarget.parentElement); };
category.onmouseout = function(e) { enableEffect (e.currentTarget.parentElement); };
};
} // end function (idx,el,scope)
); // end forEachEntryIn
// Helper functions to keep main loop readable
function enableEffect(parent) {
if (!parent.classList.contains('effect')) { // if parent has no '.effect'
parent.classList.add('effect'); // then add it
};
if (DEBUG) logInfo(parent);
};
function disableEffect(parent) {
if (parent.classList.contains('effect')) { // parent if has '.effect'
parent.classList.remove('effect'); // then remove it
};
if (DEBUG) logInfo(parent);
};
// For debugging
function logInfo(p) {
console.log( // Show some info in browser console
((p.className) ? '<' + p.tagName +' class="' + p.className + '">': '<' + p.tagName +'>' ),
p.classList.contains('effect')
);
};
/********************************/
/* demo for CSS only solution 1 */
/********************************/
.no-card {
pointer-events: none;
}
.no-card>:not(.category) {
pointer-events: auto;
}
.no-card:active {
transform: translateX(50px);
transition: all 0.5s ease;
}
/***********************************/
/* demo for CSS plus JS solution 2 */
/***********************************/
/* class will be assigned with JS */
.effect:active {
transform: translateX(50px);
transition: all 0.5s ease;
}
/*****************************************************/
/* below just demo, everything can be safely removed */
/*****************************************************/
/**************************/
/* preferred global rules */
/**************************/
html,body { box-sizing: border-box; width: 100%; max-width: 100% }
*::before,*::after, * { box-sizing: inherit }
body { margin: 0 }
/* ALL math reference: https://www.mathsisfun.com/equation_of_line.html */
/* responsive base font size using y = mx + b */
html { font-size: calc(0.625vmin + 0.75rem) } /* (320,14)(1280,20) */
/* prohibit user from selecting text (put in <body>) */
[no-select] { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none }
[do-select] { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: auto }
/* enable user to select text (put in specific elements) */
/* to show all elements with outlines (assigned to <body> with JS) */
[outlines="1"] * { outline: 1px dashed }
/***********************************/
/* Extra: plain responsive columns */
/***********************************/
body {
/*
responsive page padding using y = mx + b
p1(320,32) p2(1920, 72) => y = 0.025x + 24
p3(320, 8) p4(1920,320) => y = 0.195x - 54.4
*/
padding: calc(2.5vh + 24px) calc(19.5vw - 54.4px);
}
.cardList {
column-count: 3; /* preferred number of columns given column-width */
column-gap: 0; /* handled with card margins */
/*
column width using y = mx + b
mobile/tablet, 1 column : 320 - 60 = 260px
desktop, 3 columns: (1920 - 640) / 3 = 426 minus animation gap = 376px
p1(320,260) p2(1920,376)
=> y = 7.25x + 236.8
*/
column-width: calc(7.25vw + 230.8px); /* (320,260)(1920,376) */
/* (320,260)(1920,376) for scrollbar => 236.8 - (18/3) = 230.8px */
}
.card {
break-inside: avoid; /* don't split card over columns */
}
/******************/
/* card eye-candy */
/******************/
.wrapper,
.cardList {
background-color: rgba(0,0,0,.1); /* just to review body padding */
padding: 2rem 0;
}
.no-card, .card {
background-color: CornSilk;
padding: 1rem;
margin : 1rem;
margin-right: 60px; /* animation width plus 10px space */
/* GMC elevation 1dp */
box-shadow: 0px 2px 1px -1px rgba(0,0,0,.20),
0px 1px 1px 0px rgba(0,0,0,.14),
0px 1px 3px 0px rgba(0,0,0,.12);
}
.card:first-child { margin-top: 0 } /* otherwise jagged column tops */
/* Some :hover animation */
.no-card:hover, .card:hover {
/* GMC elevation 3dp */
box-shadow: 0px 3px 3px -2px rgba(0,0,0,.20),
0px 3px 4px 0px rgba(0,0,0,.14),
0px 1px 8px 0px rgba(0,0,0,.12);
}
<body no-select>
<h2>solution 1, CSS only</h2>
<div class="wrapper">
<div class="no-card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">no-card</div>
<div class="description">description</div>
</div>
</div>
<h2>solution 2, CSS plus JS</h2>
<div class="cardList">
<div class="card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">card 1</div>
<div class="description">description</div>
</div>
<div class="card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">card 2</div>
<div class="description">description</div>
</div>
<div class="card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">card 3</div>
<div class="description">description</div>
</div>
<div class="card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">card 4</div>
<div class="description">description</div>
</div>
<div class="card">
<div class="img">image</div>
<div>some other element</div>
<div class="title">card 5</div>
<div class="description">description</div>
</div>
<div class="card">
<div class="img">image</div>
<button class="category">CATEGORY</button>
<div class="title">card 6</div>
<div class="description">description</div>
</div>
</div>
</body>
Related
How can i make when I click on the card it opens a "modal window" with information about the product.
<div class="card">
<div class="imgBox">
<img src="./img/bau.png" alt="Produto" class="mouse">
</div>
<div class="contentBox">
<h3>Plugin</h3>
<h2 class="price">25.<small>00</small> BRL</h2>
Comprar Agora!
</div>
</div>
There are several approaches. And there is no real right or wrong here either.
The approach has to fit your application. If you always try to keep the approach somewhat abstract, there is nothing fundamentally wrong with it.
In the example below, I have taken the linked modal example from the comment
below your question and adapted the following.
added a data object in which I manage the corresponding contents of the modal.Here you can also use an API call against an interface.
I have assigned an EventListener to all buttons.
The parts that are variable in the modal are exchanged with the corresponding content when clicked.
Done!
const modalData = [
{id: 1, title: "Title One", content: "bla"},
{id: 2, title: "Title Two", content: "bla blu"},
];
// Get the modal
var modal = document.getElementById("myModal");
// Get the button that opens the modal
var btns = document.querySelectorAll(".myBtn");
// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];
// When the user clicks the button, open the modal
btns.forEach(b => {
b.addEventListener('click', (e) => {
modal.style.display = "block";
const dataId = e.target.getAttribute("data-id")
const data = modalData.filter(m => m.id == dataId);
const modalTitle = document.querySelector("#myModal .title");
const modalContent = document.querySelector("#myModal .content");
modalTitle.innerHTML = data[0].title;
modalContent.innerHTML = data[0].content;
})
});
// When the user clicks on <span> (x), close the modal
span.onclick = function() {
modal.style.display = "none";
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
body {font-family: Arial, Helvetica, sans-serif;}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 80%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
#-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
#keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.close {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
.modal-header {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
}
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
}
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Animated Modal with Header and Footer</h2>
<!-- Trigger/Open The Modal -->
<button class="myBtn" data-id="1">Open Modal 1</button>
<button class="myBtn" data-id="2">Open Modal 2</button>
<!-- The Modal -->
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="close">×</span>
<h2 class="title">Modal Header</h2>
</div>
<div class="modal-body content">
</div>
<div class="modal-footer">
<h3>Modal Footer</h3>
</div>
</div>
</div>
</body>
This question already has answers here:
Stop CSS transition from firing on page load
(9 answers)
Closed 3 months ago.
I have a popup element which is hidden by default and only shows up programmatically when the script assigns a specific class to its container and populates the popup text.
In css/stylesheet.css:
.error-message {
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
}
.container.with-error .error-message {
opacity: 1;
visibility: visible;
}
In index.html:
<link rel="stylesheet" href="css/stylesheet.css">
<div class="container">
<div class="error-message">This text will be changed by a script.</div>
</div>
According to this simple style declaration, the .error-message element should always be invisible, unless it is preceded by a .container.with-error, in which case it becomes visible, and its appearance is always animated because of transition property.
However, the .error-message triggers its transition when the page is loaded, resulting in a flash which I believe it should not do.
Related behavior I have observed:
The flash does not appear if the style is declared in an inline <style> tag
The flash appears if every style but transition: all is declared in an inline <style> tag
The flash does not appear if the style is loaded from a Base-64 encoded Data URL like this: <link href="data:text/css;base64,...">
The flash does not appear if the style loaded from <link rel="stylesheet"> is retrieved from cache.
I've created a demo that reproduces this bug every time. To simulate requesting a remote stylesheet without cache, a blob:// Object URL is generated from the style instead. The inline demo is available at the end of this question, but for best results, use JSBin. Use F5 to see the bug in action.
I'm curious how to fix this and what causes this issue as this is clearly not intended behavior.
<!doctype html>
<html lang="en">
<head>
<script>
/* jshint browser: true, esversion: 6 */
window.onload = function() {
// Log all transition events
window.ontransitioncancel = appendToTransitionLog;
window.ontransitionstart = appendToTransitionLog;
window.ontransitionrun = appendToTransitionLog;
window.ontransitionend = appendToTransitionLog;
// Simulates loading a stylesheet from a remote location
// Works the same way as if #simulated-stylesheet's content
// was hosted and served from <link rel=stylesheet> without cache
//
// Keep in mind that this bug does not appear
// if the style is injected or loaded from cache!
createFakeStylesheet();
};
function createFakeStylesheet() {
var styleContent = document.getElementById("simulated-stylesheet").text;
var styleBlob = new Blob([styleContent], {type: "text/css"});
var styleURL = URL.createObjectURL(styleBlob);
var linkElement = document.createElement("link");
linkElement.rel = "stylesheet";
linkElement.href = styleURL;
document.head.appendChild(linkElement);
}
// Functions below handle transition events logging
// Template import helper
function importTemplateFromId(id) {
return document.importNode(document.getElementById(id).content, true);
}
// Returns a string like "div.class1.class2" to describe an element
function describeElement(element) {
var tagName = element.tagName.toLowerCase();
var classes = element.classList.toString().split(" ").filter(className => className != "").map(className => "." + className).join("");
return tagName + classes;
}
// Returns a matching log group wrapper.
// The wrapper is created if the group does not exists.
// Used for grouping transition events by element descriptor
function getLogWrapper(logContainer, elementText) {
var matchingWrapper = logContainer.querySelector(".wrapper[data-for-element=\"" + elementText + "\"] .logs");
if (matchingWrapper) {
return matchingWrapper;
}
var wrapperTemplate = importTemplateFromId("wrapper-template");
var wrapperName = wrapperTemplate.querySelector(".name");
var wrapperElement = wrapperTemplate.querySelector(".wrapper");
wrapperName.textContent = elementText;
wrapperElement.dataset.forElement = elementText;
return logContainer.appendChild(wrapperElement).querySelector(".logs");
}
// Logs a transition event.
// Logs are grouped by each event type (start, run, end)
// and target element's descriptor (see describeElement)
function appendToTransitionLog(transitionEvent) {
var eventType = transitionEvent.type;
var eventProperty = transitionEvent.propertyName;
var logContainer = document.getElementById("log-" + eventType);
var elementText = describeElement(transitionEvent.target);
var logWrapper = getLogWrapper(logContainer, elementText);
var logEntry = document.createElement("span");
logEntry.textContent = eventProperty;
logEntry.className = "entry";
logWrapper.appendChild(logEntry);
}
</script>
<style>
#edit-with-js-bin {
display: none!important;
}
.log {
font-size: 14px;
}
.log .wrapper {
padding-left: 16px;
}
.wrapper .name {
text-decoration: underline;
}
.wrapper .logs {
padding-left: 12px;
}
.wrapper .entry {
display: inline-block;
color: grey;
padding: 8px 4px;
}
.wrapper .entry:nth-child(2n) {
color: lightgrey;
}
body {
font-family: monospace;
font-size: 0;
}
.side {
display: inline-block;
font-size: 14px;
vertical-align: top;
width: 50%;
height: 100%;
}
</style>
<template id="wrapper-template">
<div class="wrapper" data-for>
<span class="name"></span>
<div class="logs"></div>
</div>
</template>
<script id="simulated-stylesheet" type="text/css">
.remote {
background: crimson;
color: white;
display: inline-block;
margin: 8px;
padding: 8px;
}
.remote.transparent {
opacity: 0;
visibility: hidden;
}
.remote.transition-some {
transition: opacity, visibility 1s ease;
}
.remote.transition-all {
transition: all 1s ease;
}
</script>
<style>
.transition-all-inline {
transition: all 1s ease;
}
.local {
background: green;
color: white;
display: inline-block;
margin: 8px;
padding: 8px;
}
.local.transparent {
opacity: 0;
visibility: hidden;
}
.local.transition-some {
transition: opacity, visibility 1s ease;
}
.local.transition-all {
transition: all 1s ease;
}
.mock {
background: orangered;
transition: all 1s ease;
}
.mock:hover {
background: orange;
}
</style>
</head>
<body>
<div class="side left">
<div>
<u>.remote</u> <div class="remote">I'm always styled.</div>
</div>
<div>
.remote<u>.transparent</u> <div class="remote transparent">I'm always transparent.</div>
</div>
<div>
.remote.transparent<u>.transition-some</u> <div class="remote transparent transition-some">I'm invisible!</div>
</div>
<div>
.remote.transparent<u>.transition-all</u> <div class="remote transparent transition-all">I will briefly flash when the page loads.</div>
</div>
<div>
.remote.transparent<u>.transition-all-inline</u> <div class="remote transparent transition-all-inline">I will briefly flash when the page loads.</div>
</div>
</div>
<div class="side right">
<div>
<u>.local</u> <div class="local">I'm always styled.</div>
</div>
<div>
.local<u>.transparent</u> <div class="local transparent">I'm always transparent.</div>
</div>
<div>
.local.transparent<u>.transition-some</u> <div class="local transparent transition-some">I'm invisible!</div>
</div>
<div>
.local.transparent<u>.transition-all</u> <div class="local transparent transition-all">I'm invisible!</div>
</div>
<div>
.local.transparent<u>.transition-all-inline</u> <div class="local transparent transition-all-inline">I'm invisible!</div>
</div>
<div>
.local.mock <div class="local mock">Use me to debug transition events!</div>
</div>
</div>
<div class="log">
<div>
<b>ontransitionstart</b>
<div id="log-transitionstart"></div>
</div>
<div>
<b>ontransitionrun</b>
<div id="log-transitionrun"></div>
</div>
<div>
<b>ontransitionend</b>
<div id="log-transitionend"></div>
</div>
<div>
<b>ontransitioncancel</b>
<div id="log-ontransitioncancel"></div>
</div>
</div>
</body>
</html>
EDIT: The flash appears regardless of what property is being transitioned.
This still creates the same effect:
.error-message {
transition: opacity 0.2s ease;
}
It's a normal behavior of transition (not a bug of any specific browser).
The issue in your case is that you already have elements (that flashes) in the DOM tree.
That also means the elements have an initial state and any new state performs a transition. If a new style applied and it has a transition property a browser will show you animation between the initial state and the new state with a transition property. (Note that the initial state of the element that flashes is not transparent).
The possible fix for that is to add hidden elements when new styles already exist. Or refactor your styles to have a transparent initial state.
I am currently following the W3 HTML/CSS tutorial on getting multiple slideshows on one page. The tutorial has examples for multiple slideshows (manual) or ONE automatic slideshow, but no examples for multiple (automatic) slideshows. I attempted to combine the tutorial, keeping in mind that the indexes for each individual slideshow needed to be intact, but no matter what I do, it would not automatically transition.
This is the link to the W3 tutorial: https://www.w3schools.com/howto/howto_js_slideshow.asp(https://www.w3schools.com/howto/howto_js_slideshow.asp)
This is the code that I attempted to change/use to automatically transition the slides:
var slideIndex = [1,1];
/* Class the members of each slideshow group with different CSS classes */
var slideId = ["mySlides1", "mySlides2"]
showSlides(1, 0);
showSlides(1, 1);
function plusSlides(n, no) {
showSlides(slideIndex[no] += n, no);
}
function showSlides(n, no) {
var i;
var x = document.getElementsByClassName(slideId[no]);
if (n > x.length) {slideIndex[no] = 1}
if (n < 1) {slideIndex[no] = x.length}
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
n++; /* WHAT I ADDED */
x[slideIndex[no]-1].style.display = "block";
setTimeout(showSlides(n, x), 2000); /* WHAT I ADDED */
}
The capitalized comment next to the code line is what I added to the tutorial code. Otherwise, the rest of the code is exactly the same as the tutorial with no changes. I have tried setInterval and moving the code in different spots, but nothing works.
Just a couple of changes and you are there. I also added an option to change delays, take a look:
/* Find all slideshow containers */
var slideshowContainers = document.getElementsByClassName("slideshow-container");
/* For each container get starting variables */
for(let s = 0; s < slideshowContainers.length; s++) {
/* Read the new data attribute */
var cycle = slideshowContainers[s].dataset.cycle;
/* Find all the child nodes with class mySlides */
var slides = slideshowContainers[s].querySelectorAll('.mySlides');
var slideIndex = 0;
/* Now we can cycle slides, but this recursive function must have parameters */
/* slides and cycle never change, those are unique for each slide container */
/* slideIndex will increase during each iteration */
showSlides(slides, slideIndex, cycle);
};
/* Function is alsmost same, but now it uses 3 new parameters */
function showSlides(slides, slideIndex, cycle) {
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
};
slideIndex++;
if (slideIndex > slides.length) {
slideIndex = 1
};
slides[slideIndex - 1].style.display = "block";
/* Calling same function, but with new parameters and cycle time */
setTimeout(function() {
showSlides(slides, slideIndex, cycle)
}, cycle);
};
* {
box-sizing: border-box;
}
body {
font-family: Verdana, sans-serif;
}
.mySlides {
display: none;
}
img {
vertical-align: middle;
}
/* Slideshow container */
.slideshow-container {
max-width: 320px;
position: relative;
margin: auto;
}
/* Caption text */
.text {
color: #f2f2f2;
font-size: 15px;
padding: 8px 12px;
position: absolute;
bottom: 8px;
width: 100%;
text-align: center;
}
/* Number text (1/3 etc) */
.numbertext {
color: #f2f2f2;
font-size: 12px;
padding: 8px 12px;
position: absolute;
top: 0;
}
/* The dots/bullets/indicators */
.dot {
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
}
.active {
background-color: #717171;
}
/* Fading animation */
.fade {
-webkit-animation-name: fade;
-webkit-animation-duration: 1.5s;
animation-name: fade;
animation-duration: 1.5s;
}
#-webkit-keyframes fade {
from {
opacity: .4
}
to {
opacity: 1
}
}
#keyframes fade {
from {
opacity: .4
}
to {
opacity: 1
}
}
/* On smaller screens, decrease text size */
#media only screen and (max-width: 300px) {
.text {
font-size: 11px
}
}
<h2>Automatic Slideshow</h2>
<p>Change image every 2 seconds:</p>
<div class="slideshow-container" data-cycle="2000">
<div class="mySlides fade">
<div class="numbertext">1 / 3</div>
<img src="https://placeimg.com/320/240/animals">
<div class="text">Caption Text</div>
</div>
<div class="mySlides fade">
<div class="numbertext">2 / 3</div>
<img src="https://placeimg.com/320/240/nature">
<div class="text">Caption Two</div>
</div>
<div class="mySlides fade">
<div class="numbertext">3 / 3</div>
<img src="https://placeimg.com/320/240/people">
<div class="text">Caption Three</div>
</div>
</div>
<p>Change image every 3 seconds:</p>
<div class="slideshow-container" data-cycle="3000">
<div class="mySlides fade">
<div class="numbertext">1 / 2</div>
<img src="https://placeimg.com/320/240/animals">
<div class="text">Caption 1</div>
</div>
<div class="mySlides fade">
<div class="numbertext">2 / 2</div>
<img src="https://placeimg.com/320/240/nature">
<div class="text">Caption 2</div>
</div>
</div>
Also on JSFiddle
What is most important here is the scope of Javascript variables. I suggest reading about JS Variables, than google javascript scope variables and you may find articles like this one, or that one.
Here is an example chat app ->
The idea here is to have the .messages-container take up as much of the screen as it can. Within .messages-container, .scroll holds the list of messages, and in case there are more messages then the size of the screen, scrolls.
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Now, instead of the user staying scrolled to the bottom of the conversation, the text-input increases, and they no longer see the bottom.
One way to fix it, if we are using react, calculate the height of text-input, and if anything changes, let .messages-container know
componentDidUpdate() {
window.setTimeout(_ => {
const newHeight = this.calcHeight();
if (newHeight !== this._oldHeight) {
this.props.onResize();
}
this._oldHeight = newHeight;
});
}
But, this causes visible performance issues, and it's sad to be passing messages around like this.
Is there a better way? Could I use css in such a way, to express that when .text-input-increases, I want to essentially shift up all of .messages-container
2:nd revision of this answer
Your friend here is flex-direction: column-reverse; which does all you ask while align the messages at the bottom of the message container, just like for example Skype and many other chat apps do.
.chat-window{
display:flex;
flex-direction:column;
height:100%;
}
.chat-messages{
flex: 1;
height:100%;
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }
The downside with flex-direction: column-reverse; is a bug in IE/Edge/Firefox, where the scrollbar doesn't show, which your can read more about here: Flexbox column-reverse and overflow in Firefox/IE
The upside is you have ~ 90% browser support on mobile/tablets and ~ 65% for desktop, and counting as the bug gets fixed, ...and there is a workaround.
// scroll to bottom
function updateScroll(el){
el.scrollTop = el.scrollHeight;
}
// only shift-up if at bottom
function scrollAtBottom(el){
return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
In the below code snippet I've added the 2 functions from above, to make IE/Edge/Firefox behave in the same way flex-direction: column-reverse; does.
function addContent () {
var msgdiv = document.getElementById('messages');
var msgtxt = document.getElementById('inputs');
var atbottom = scrollAtBottom(msgdiv);
if (msgtxt.value.length > 0) {
msgdiv.innerHTML += msgtxt.value + '<br/>';
msgtxt.value = "";
} else {
msgdiv.innerHTML += 'Long long content ' + (tempCounter++) + '!<br/>';
}
/* if at bottom and is IE/Edge/Firefox */
if (atbottom && (!isWebkit || isEdge)) {
updateScroll(msgdiv);
}
}
function resizeInput () {
var msgdiv = document.getElementById('messages');
var msgtxt = document.getElementById('inputs');
var atbottom = scrollAtBottom(msgdiv);
if (msgtxt.style.height == '120px') {
msgtxt.style.height = 'auto';
} else {
msgtxt.style.height = '120px';
}
/* if at bottom and is IE/Edge/Firefox */
if (atbottom && (!isWebkit || isEdge)) {
updateScroll(msgdiv);
}
}
/* fix for IE/Edge/Firefox */
var isWebkit = ('WebkitAppearance' in document.documentElement.style);
var isEdge = ('-ms-accelerator' in document.documentElement.style);
var tempCounter = 6;
function updateScroll(el){
el.scrollTop = el.scrollHeight;
}
function scrollAtBottom(el){
return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
html, body { height:100%; margin:0; padding:0; }
.chat-window{
display:flex;
flex-direction:column;
height:100%;
}
.chat-messages{
flex: 1;
height:100%;
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }
/* temp. buttons for demo */
button { width: 12%; height: 44px; margin-left: 5%; vertical-align: top; }
/* begin - fix for hidden scrollbar in IE/Edge/Firefox */
.chat-messages-text{ overflow: auto; }
#media screen and (-webkit-min-device-pixel-ratio:0) {
.chat-messages-text{ overflow: visible; }
/* reset Edge as it identifies itself as webkit */
#supports (-ms-accelerator:true) { .chat-messages-text{ overflow: auto; } }
}
/* hide resize FF */
#-moz-document url-prefix() { .chat-input-text { resize: none } }
/* end - fix for hidden scrollbar in IE/Edge/Firefox */
<div class="chat-window">
<div class="chat-messages">
<div class="chat-messages-text" id="messages">
Long long content 1!<br/>
Long long content 2!<br/>
Long long content 3!<br/>
Long long content 4!<br/>
Long long content 5!<br/>
</div>
</div>
<div class="chat-input">
<textarea class="chat-input-text" placeholder="Type your message here..." id="inputs"></textarea>
<button onclick="addContent();">Add msg</button>
<button onclick="resizeInput();">Resize input</button>
</div>
</div>
Side note 1: The detection method is not fully tested, but it should work on newer browsers.
Side note 2: Attach a resize event handler for the chat-input might be more efficient then calling the updateScroll function.
Note: Credits to HaZardouS for reusing his html structure
You just need one CSS rule set:
.messages-container, .scroll {transform: scale(1,-1);}
That's it, you're done!
How it works: First, it vertically flips the container element so that the top becomes the bottom (giving us the desired scroll orientation), then it flips the content element so that the messages won't be upside down.
This approach works in all modern browsers. It does have a strange side effect, though: when you use a mouse wheel in the message box, the scroll direction is reversed. This can be fixed with a few lines of JavaScript, as shown below.
Here's a demo and a fiddle to play with:
//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) {
if(e.deltaY) {
e.preventDefault();
e.currentTarget.scrollTop -= e.deltaY;
}
});
//The rest of the JS just handles the test buttons and is not part of the solution
send = function() {
var inp = document.querySelector('.text-input');
document.querySelector('.scroll').insertAdjacentHTML('beforeend', '<p>' + inp.value);
inp.value = '';
inp.focus();
}
resize = function() {
var inp = document.querySelector('.text-input');
inp.style.height = inp.style.height === '50%' ? null : '50%';
}
html,body {height: 100%;margin: 0;}
.conversation {
display: flex;
flex-direction: column;
height: 100%;
}
.messages-container {
flex-shrink: 10;
height: 100%;
overflow: auto;
}
.messages-container, .scroll {transform: scale(1,-1);}
.text-input {resize: vertical;}
<div class="conversation">
<div class="messages-container">
<div class="scroll">
<p>Message 1<p>Message 2<p>Message 3<p>Message 4<p>Message 5
<p>Message 6<p>Message 7<p>Message 8<p>Message 9<p>Message 10<p>Message 11<p>Message 12<p>Message 13<p>Message 14<p>Message 15<p>Message 16<p>Message 17<p>Message 18<p>Message 19<p>Message 20
</div>
</div>
<textarea class="text-input" autofocus>Your message</textarea>
<div>
<button id="send" onclick="send();">Send input</button>
<button id="resize" onclick="resize();">Resize input box</button>
</div>
</div>
Edit: thanks to #SomeoneSpecial for suggesting a simplification to the scroll code!
Please try the following fiddle - https://jsfiddle.net/Hazardous/bypxg25c/. Although the fiddle is currently using jQuery to grow/resize the text area, the crux is in the flex related styles used for the messages-container and input-container classes -
.messages-container{
order:1;
flex:0.9 1 auto;
overflow-y:auto;
display:flex;
flex-direction:row;
flex-wrap:nowrap;
justify-content:flex-start;
align-items:stretch;
align-content:stretch;
}
.input-container{
order:2;
flex:0.1 0 auto;
}
The flex-shrink value is set to 1 for .messages-container and 0 for .input-container. This ensures that messages-container shrinks when there is a reallocation of size.
I've moved text-input within messages, absolute positioned it to the bottom of the container and given messages enough bottom padding to space accordingly.
Run some code to add a class to conversation, which changes the height of text-input and bottom padding of messages using a nice CSS transition animation.
The JavaScript runs a "scrollTo" function at the same time as the CSS transition is running to keep the scroll at the bottom.
When the scroll comes off the bottom again, we remove the class from conversation
Hope this helps.
https://jsfiddle.net/cnvzLfso/5/
var doScollCheck = true;
var objConv = document.querySelector('.conversation');
var objMessages = document.querySelector('.messages');
var objInput = document.querySelector('.text-input');
function scrollTo(element, to, duration) {
if (duration <= 0) {
doScollCheck = true;
return;
}
var difference = to - element.scrollTop;
var perTick = difference / duration * 10;
setTimeout(function() {
element.scrollTop = element.scrollTop + perTick;
if (element.scrollTop === to) {
doScollCheck = true;
return;
}
scrollTo(element, to, duration - 10);
}, 10);
}
function resizeInput(atBottom) {
var className = 'bigger',
hasClass;
if (objConv.classList) {
hasClass = objConv.classList.contains(className);
} else {
hasClass = new RegExp('(^| )' + className + '( |$)', 'gi').test(objConv.className);
}
if (atBottom) {
if (!hasClass) {
doScollCheck = false;
if (objConv.classList) {
objConv.classList.add(className);
} else {
objConv.className += ' ' + className;
}
scrollTo(objMessages, (objMessages.scrollHeight - objMessages.offsetHeight) + 50, 500);
}
} else {
if (hasClass) {
if (objConv.classList) {
objConv.classList.remove(className);
} else {
objConv.className = objConv.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
}
}
objMessages.addEventListener('scroll', function() {
if (doScollCheck) {
var isBottom = ((this.scrollHeight - this.offsetHeight) === this.scrollTop);
resizeInput(isBottom);
}
});
html,
body {
height: 100%;
width: 100%;
background: white;
}
body {
margin: 0;
padding: 0;
}
.conversation {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
position: relative;
}
.messages {
overflow-y: scroll;
padding: 10px 10px 60px 10px;
-webkit-transition: padding .5s;
-moz-transition: padding .5s;
transition: padding .5s;
}
.text-input {
padding: 10px;
-webkit-transition: height .5s;
-moz-transition: height .5s;
transition: height .5s;
position: absolute;
bottom: 0;
height: 50px;
background: white;
}
.conversation.bigger .messages {
padding-bottom: 110px;
}
.conversation.bigger .text-input {
height: 100px;
}
.text-input input {
height: 100%;
}
<div class="conversation">
<div class="messages">
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is the last message
</p>
<div class="text-input">
<input type="text" />
</div>
</div>
</div>
You write;
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Wouldn't the method that dynamically sets the .text-input be the logical place to fire this.props.onResize().
To whom it may concern,
The answers above did not suffice my question.
The solution I found was to make my innerWidth and innerHeight variable constant - as the innerWidth of the browser changes on scroll to adapt for the scrollbar.
var innerWidth = window.innerWidth
var innerHeight = window.innerHeight
OR FOR REACT
this.setState({width: window.innerWidth, height: window.innerHeight})
In other words, to ignore it, you must make everything constant as if it were never scrolling. Do remember to update these on Resize / Orientation Change !
IMHO current answer is not a correct one:
1/ flex-direction: column-reverse; reverses the order of messages - I didn't want that.
2/ javascript there is also a bit hacky and obsolete
If you want to make it like a PRO use spacer-box which has properties:
flex-grow: 1;
flex-basis: 0;
and is located above messages. It pushes them down to the chat input.
When user is typing new messages and input height is growing the scrollbar moves up, but when the message is sent (input is cleared) scrollbar is back at bottom.
Check my snippet:
body {
background: #ccc;
}
.chat {
display: flex;
flex-direction: column;
width: 300px;
max-height: 300px;
max-width: 90%;
background: #fff;
}
.spacer-box {
flex-basis: 0;
flex-grow: 1;
}
.messages {
display: flex;
flex-direction: column;
overflow-y: auto;
flex-grow: 1;
padding: 24px 24px 4px;
}
.footer {
padding: 4px 24px 24px;
}
#chat-input {
width: 100%;
max-height: 100px;
overflow-y: auto;
border: 1px solid pink;
outline: none;
user-select: text;
white-space: pre-wrap;
overflow-wrap: break-word;
}
<div class="chat">
<div class="messages">
<div class="spacer-box"></div>
<div class="message">1</div>
<div class="message">2</div>
<div class="message">3</div>
<div class="message">4</div>
<div class="message">5</div>
<div class="message">6</div>
<div class="message">7</div>
<div class="message">8</div>
<div class="message">9</div>
<div class="message">10</div>
<div class="message">11</div>
<div class="message">12</div>
<div class="message">13</div>
<div class="message">14</div>
<div class="message">15</div>
<div class="message">16</div>
<div class="message">17</div>
<div class="message">18</div>
</div>
<div class="footer">
<div contenteditable role="textbox" id="chat-input"></div>
</div>
<div>
Hope I could help :)
Cheers
Having a table with draggable rows where each row is draggable=true, how can the user still be able to select text from a column?
<table>
<thead>..</thead>
<tbody>
..
<tr draggable="true">
<td>..</td>
<td>Cool text but you can't select me</td>
<td>..</td>
</tr>
..
</tbody>
</table>
Another simple example (https://codepen.io/anon/pen/qjoBXV)
div {
padding: 20px;
margin: 20px;
background: #eee;
}
.all-copy p {
-webkit-user-select: all; /* Chrome all / Safari all */
-moz-user-select: all; /* Firefox all */
-ms-user-select: all; /* IE 10+ */
user-select: all; /* Likely future */
}
<div class="all-copy" draggable="true">
<p>Select me as text</p>
</div>
There are two things we need to do.
One thing is limitting the drag event only trigger on specified area, for example, the drag handle.
The other thing is that we only set the text on the div with content class can be selected. The reason why we do so is that the element that has been set to draggable, on which browser will add a default rule user-select: none.
const itemEl = document.querySelector('.item');
const handleEl = document.querySelector('.handle');
let mouseDownEl;
itemEl.onmousedown = function(evt) {
mouseDownEl = evt.target;
}
itemEl.ondragstart = function(evt) {
// only the handle div can be picked up to trigger the drag event
if (mouseDownEl.matches('.handle')) {
// ...code
} else {
evt.preventDefault();
}
}
.item {
width: 70px;
border: 1px solid black;
text-align: center;
}
.content {
border-top: 1px solid gray;
user-select: text;
}
<div class="item" draggable="true">
<div class='handle'>handle</div>
<div class='content'>content</div>
</div>
One way to make that work, is to actually check which element fired the event, e.target, against the element that has the listener attach to itself, #draggable (in this case using this).
if (e.target === this) {...}
This will allow default behavior on element positioned inside the draggable element, such as selecting a text and so on.
Note, since Firefox has issue with draggable="true", I used a different drag method.
Stack snippet
(function (elem2drag) {
var x_pos = 0, y_pos = 0, x_elem = 0, y_elem = 0;
document.querySelector('#draggable').addEventListener('mousemove', function(e) {
x_pos = e.pageX;
y_pos = e.pageY;
if (elem2drag !== null) {
elem2drag.style.left = (x_pos - x_elem) + 'px';
elem2drag.style.top = (y_pos - y_elem) + 'px';
}
})
document.querySelector('#draggable').addEventListener('mousedown', function(e) {
if (e.target === this) {
elem2drag = this;
x_elem = x_pos - elem2drag.offsetLeft;
y_elem = y_pos - elem2drag.offsetTop;
return false;
}
})
document.querySelector('#draggable').addEventListener('mouseup', function(e) {
elem2drag = null;
})
})(null);
#draggable {
display: inline-block;
background: lightgray;
padding:15px;
cursor:move;
position:relative;
}
span {
background: white;
line-height: 25px;
cursor:auto;
}
<div id="draggable">
<span>Select me as text will work<br>when the mouse is over the text</span>
</div>