How to replicate the Material Ripple animation in Vuejs? - javascript

I am trying to replicate the ripple effect from Material Design since the current app I am working on is going to get rid of Quasar; so I'm building all the elements from the ground up.
This effect:
I've watched a few videos doing this in just pure CSS and JS and I've tried to convert it into my project, but am getting caught up somewhere. I have the hover effect and the mouse location logging properly, but the ripple effect is just not triggering and I don't know why.
Any help would be greatly appreciated! Cheers!
CodeSandbox Code
CButton.vue
<template>
<button
#click="onClick"
:class="[
'c-btn',
`c-btn--${kind}`,
disabled ? `_disabled` : '',
kind === 'icon-round' ? 'shadow-5' : '',
]"
>
<transition
name="ripple"
#enter="rippleEnter"
#after-enter="afterRippleEnter"
>
<span v-if="ripple" ref="ripple" class="ripple" />
</transition>
<div class="_inner">
<div class="_text">
<slot>{{ btnText }}</slot>
</div>
</div>
</button>
</template>
<script>
export default {
name: "CcBtn",
components: {},
props: {
btnText: { type: String },
kind: { type: String, default: "main" },
isBusy: { type: Boolean, default: false },
/**
* HTML5 attribute
* #category state
*/
disabled: { type: Boolean, default: false },
color: { type: String, default: "" },
},
data() {
return {
ripple: false,
x: 0,
y: 0,
};
},
methods: {
onClick(e) {
this.x = e.layerX;
this.y = e.layerY;
this.ripple = !this.ripple;
console.log(`x`, this.x);
console.log(`y`, this.y);
console.log(`ripple`, this.ripple);
},
rippleEnter() {
this.$refs.ripple.style.top = `${this.y}px`;
this.$refs.ripple.style.left = `${this.x}px`;
},
afterRippleEnter() {
this.ripple = false;
},
},
};
</script>
<style lang="sass" scoped>
.c-btn
color: white
padding: 10px 16px
border-radius: 4px
line-height: 1em
min-height: 2em
font-weight: bold
font-size: 16px
color: White
cursor: pointer
border: 1px solid transparent
transition: background-color 0.5s
._inner
display: flex
align-items: center
justify-content: center
&--main
background: #9759ff
min-width: 228px
border-radius: 100px
&:hover
background-color: lighten(#9759ff, 10%)
&--sub
background: #f3eefe
min-width: 228px
border-radius: 100px
color: black
&:hover
background-color: darken(#f3eefe, 5%)
.ripple
display: block
width: 20px
height: 20px
border-radius: 10px
position: absolute
top: 0
left: 0
pointer-events: none
background-color: rgba(lighten(#9759ff, 20%), 0.8)
opacity: 0
transform: translate(-50%, -50%) scale(10)
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out
&-enter
opacity: 1
transform: translate(-50%, -50%) scale(0)
</style>
App.vue
<template>
<CButton :btnText="'Button'" kind="main" />
<br />
<br />
<br />
<CButton :btnText="'Button'" kind="sub" />
</template>
<script>
import CButton from "./components/CButton.vue";
export default {
name: "App",
components: {
CButton,
},
};
</script>

Here is a working code for a button to have a ripple effect on click. Using CSS and JS:
function createRipple(event) {
const button = event.currentTarget;
const circle = document.createElement("span");
const diameter = Math.max(button.clientWidth, button.clientHeight);
const radius = diameter / 2;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
circle.classList.add("ripple");
const ripple = button.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
button.appendChild(circle);
}
const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
button.addEventListener("click", createRipple);
}
body {
height: 100vh;
margin: 0;
display: grid;
place-items: center;
}
#import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
button {
position: relative;
overflow: hidden;
transition: background 400ms;
color: #fff;
background-color: #ff0000;
padding: 1rem 2rem;
font-family: 'Roboto', sans-serif;
font-size: 1.5rem;
outline: 0;
border: 0;
border-radius: 0.25rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3); /* black with 30% opacity */
cursor: pointer;
}
span.ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple 600ms linear;
background-color: rgba(255, 255, 255, 0.7);
}
#keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
<button>Click For Effect</button>

Related

On mobile phone images sometimes doesnt load

Problem
In my app I have card with projects and sometimes images of those projects doesn't load on mobile i have to reload browser and to it is loaded correctly I have no idea how.I must also point out that on the computer viewer everything works correctly.
Stack
I`m using Astro.js with Preact.
Here is demonstration video of my problem
https://streamable.com/okquzk
My code
The way i use it. In Astro framework you can create collections with you mdx or md data here i pass route to imgs like this:
Note: in Astro you dont have to write public/something it ok to write
/projects/bhn.webp
---
title: "Black Hat News"
heroImage: /projects/bhn.webp
___
After some step I am using it inside Card.tsx
import "./styles/card.css";
type Props = {
title: string;
heroImage: string;
slug: string;
};
export const Card = ({ title, heroImage, slug }: Props) => (
<a href={slug}>
<div class="card">
<img src={heroImage} alt={title} />
<div class="info">
<h1>{title}</h1>
</div>
</div>
</a>
);
and there is css
/* Set padding top to make a trick with aspect ratio 16:9 */
.card {
padding: 1rem;
width: 100%;
padding-top: 56.25%;
position: relative;
display: flex;
align-items: flex-end;
transition: 0.4s ease-out;
box-shadow: 0px 7px 10px rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.card:before {
content: "";
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
/* background: rgba(0, 0, 0, 0.6); */
z-index: 2;
}
.card img {
width: 100%;
height: 100%;
-o-object-fit: cover;
object-fit: cover;
object-position: center top;
position: absolute;
top: 0;
left: 0;
}
.card .info {
position: relative;
z-index: 3;
color: var(--color-text);
background: var(--color-dark);
padding: 0.4rem;
border-radius: 5px;
}
.card .info h1 {
font-size: var(--font-lg);
}
/* for desktop add nice effects */
#media (min-width: 1024px) {
.card:hover {
transform: translateY(10px);
}
.card:hover:before {
opacity: 1;
background: rgba(0, 0, 0, 0.6);
}
.card:hover .info {
opacity: 1;
transform: translateY(0px);
}
.card:before {
transition: 0.3s;
opacity: 0;
}
.card .info {
opacity: 0;
transform: translateY(30px);
transition: 0.3s;
}
.card .info h1 {
margin: 0px;
}
}
Edit
Added usage
import "./styles/projectsGrid.css";
import { useStore } from "#nanostores/preact";
import { getProjectsByTag } from "#utils/*";
import type { CollectionEntry } from "astro:content";
import { tagValue } from "src/tagStore";
import { Card } from "./Card";
type Props = {
projects: CollectionEntry<"projects">[];
};
export const ProjectsGrid = ({ projects }: Props) => {
const $tagValue = useStore(tagValue);
const filteredProjects = getProjectsByTag(projects, $tagValue);
return (
<div class="projects_wrapper">
{filteredProjects.map(({ data: { title, heroImage }, slug }) => (
<Card title={title} heroImage={heroImage} slug={slug} />
))}
</div>
);
};

Why is my jQuery click handler acting as though I clicked on the parent element?

I created lot of child divs in parent div. All divs positioned, parent div is absolute, child divs are relative. Parent div z-index is 400, child divs are 500.
When I clicked any child div then jQuery detect parent div with clicked function. I don't understand why isn't working these codes.
So I hope anyone can help me this situation.
Parent div ID: "#cardslayer"
Child divs class: ".cardinlayer"
-HTML:
<body>
<div id="cardslayer"></div>
</body>
-CSS:
#cardslayer {
position: absolute;
width: 960px;
height: auto;
top: 0;
left: 0;
z-index: 400;
display: none;
}
.cardinlayer {
width: 100px;
height: 125px;
margin: 10px;
position: relative;
z-index: 500;
display: none;
}
-JQUERY: (And some style in css with jquery function.)
var hazakstr = "";
var i = 0;
$("#button").click(function(){
hazakstr = "<center>";
for(i=0; i<22; i++) {
if(level_owner[i] == -1) {
hazakstr = hazakstr + "<div class='cardinlayer' id='house" + i + "' style='background: url(../houses/" + i + ".png); background-size: 100px 125px; background-repeat: no-repeat;'></div>";
}
}
hazakstr = hazakstr + "</center>";
$("#cardslayer").css("display", "block");
$("#cardslayer").html(hazakstr);
$(".cardinlayer").css("display", "inline-block");
i=((567 - $("#cardslayer").height()) / 2);
$("#cardslayer").css("height", 567 - i + "px");
$("#cardslayer").css("padding-top", i + "px");
});
Added html to #cardslayer when loop is ended. Code like this:
HTML:
<div id="cardslayer" style="display: block; height: 507px; padding-top: 60px;">
<center>
<div class="cardinlayer" id="house0" style="background: url("../houses/0.png") 0% 0% / 100px 125px no-repeat; display: inline-block;"></div>
<div class="cardinlayer" id="house1" style="background: url("../houses/1.png") 0% 0% / 100px 125px no-repeat; display: inline-block;"></div>
.
.
.
.
<div class="cardinlayer" id="house21" style="background: url("../houses/21.png") 0% 0% / 100px 125px no-repeat; display: inline-block;"></div>
</center>
</div>
So after all, I created click function for .cardinlayer. And its not working.
$(".cardinlayer").click(function(){
alert("Cardinlayer");
});
I tried this click function for .cardinlayer
$("div").click(function(){
alert($(this).attr("id"));
});
When I clicked one .cardinlayer return value is #cardslayer, not #house1 or any #house.
#cardslayer is parent and .cardinlayer(s) are childs.
Picture of the problem: https://i.imgur.com/DjWcIKK.jpg
Red is parent, blue are childs.
So when I click any card jquery is not detect. Jquery think I clicked the faded black background. (parent).
I hope anyone can help me.
Have a nice day, and thanks.
There are modifiers to the click event, like stopPropagation or preventDefault. (More info: Event on MDN)
To see it in action:
let hazakstr = "";
const cardHtml = ({ i }) => {
return `
<div
id="house${i}"
class="cardinlayer"
data-idx="${i}"
>
HOUSE ${i}
</div>
`
}
jQuery("#button").on('click', function() {
for (let i = 0; i < 22; i++) {
hazakstr += cardHtml({ i })
}
$("#cardslayer").html(hazakstr);
});
jQuery("#cardslayer").on('click', '.cardinlayer', function(e) {
e.stopPropagation() // this stops the event from propagation
const { idx } = $(this).data()
alert(`Clicked house card: ${idx}`)
})
.container {
position: relative;
}
#cardslayer {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.cardinlayer {
display: flex;
justify-content: center;
align-items: center;
height: 125px;
padding: 8px 16px;
background-color: rgba(255, 0, 0, 0.3);
-webkit-transition: background-color 0.25s ease-out;
-moz-transition: background-color 0.25s ease-out;
-o-transition: background-color 0.25s ease-out;
transition: background-color 0.25s ease-out;
cursor: pointer;
}
.cardinlayer:hover {
background-color: rgba(255, 0, 0, 0.9)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="button">CLICK</button>
<div class="container">
<div id="cardslayer"></div>
</div>
EDIT / SUGGESTION:
I'd suggest that you don't mess with jQuery, if possible. Here's a bit updated thing in Vue:
Vue.component('HouseCard', {
props: ['idx'],
methods: {
onClick({ idx }) {
alert(`Clicked house: ${ idx }`)
},
},
template: `
<div
class="cardinlayer"
#click.stop="() => onClick({ idx })"
>
HOUSE {{ idx }}
</div>
`
})
new Vue({
el: "#app",
data() {
return {
houses: [],
}
},
methods: {
addHouse(houses) {
return [...houses, houses.length]
},
add1House() {
this.houses = this.addHouse(this.houses)
},
add22Houses() {
let ret = this.houses
for (let i = 0; i < 22; i++) {
ret = this.addHouse(ret)
}
this.houses = ret
}
},
template: `
<div>
<button #click="add1House">ADD 1 HOUSE</button>
<button #click="add22Houses">ADD 22 HOUSES</button>
<br />
<div
class="container"
>
<div
id="cardslayer"
>
<house-card
v-for="(house, idx) in houses"
:key="idx"
:idx="idx"
></house-card>
</div>
</div>
</div>
`
})
.container {
position: relative;
}
#cardslayer {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.cardinlayer {
display: flex;
justify-content: center;
align-items: center;
height: 125px;
padding: 8px 16px;
background-color: rgba(255, 0, 0, 0.3);
-webkit-transition: background-color 0.25s ease-out;
-moz-transition: background-color 0.25s ease-out;
-o-transition: background-color 0.25s ease-out;
transition: background-color 0.25s ease-out;
cursor: pointer;
}
.cardinlayer:hover {
background-color: rgba(255, 0, 0, 0.9)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
or the React solution:
const { useState } = React
const HouseCard = ({ idx }) => {
const handleClick = () => {
alert(`House clicked: ${idx}`)
}
return (
<div class="cardinlayer" onClick={handleClick}>
HOUSE { idx }
</div>
)
}
const App = () => {
const [houses, setHouses] = useState([])
const addHouse = (houses) => [...houses, houses.length]
const add1House = () => setHouses((prev) => addHouse(prev))
const add22Houses = () => {
for(let i = 0; i < 22; i++) {
setHouses((prev) => addHouse(prev))
}
}
return (
<div>
<button onClick={add1House}>ADD 1 HOUSE</button>
<button onClick={add22Houses}>ADD 22 HOUSES</button>
<div class="container">
<div id="cardslayer">
{
houses.map((_, idx) => <HouseCard idx={idx} />)
}
</div>
</div>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.container {
position: relative;
}
#cardslayer {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.cardinlayer {
display: flex;
justify-content: center;
align-items: center;
height: 125px;
padding: 8px 16px;
background-color: rgba(255, 0, 0, 0.3);
-webkit-transition: background-color 0.25s ease-out;
-moz-transition: background-color 0.25s ease-out;
-o-transition: background-color 0.25s ease-out;
transition: background-color 0.25s ease-out;
cursor: pointer;
}
.cardinlayer:hover {
background-color: rgba(255, 0, 0, 0.9)
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

Add a button to change to dark mode in html website

I have added a button on my site which let's the users change to dark or light mode whenever they want. I added the button with a moon icon on it, but the problem is that I want that the moon icon changes to sun icon when the user is in dark mode. And changes to moon icon when user is in light mode.
function myfunction(e) {
console.log("you clicked");
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
<button class="btn"><img src='moon.png'></img></button>
The .inverted class in js is because I don't want the images to invert their colors.. so I gave all the images a class='inverted'
So, this is what I've done and someone please let me know how I should change the icon to moon and sun depending on the current mode (light or dark)
Thanks!
You could add the sun as another image to the button and change the visibility of the two images via your .dark-mode css class.
So whenever the .dark-mode class is present the moon gets hidden and the sun gets shown.
function myfunction(e) {
console.log("you clicked");
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
/* button handling */
.moon {
display: block;
}
.sun {
display: none;
}
.dark-mode .moon {
display: none;
}
.dark-mode .sun {
display: block;
}
<button class="btn">
<img class="moon" src="moon.png" alt="moon"></img>
<img class="sun" src="sun.png" alt="sun"></img>
</button>
You could go with the CSS approach as in #Fabian's answer. If you would like to go with JS, you could simply use a flag to indicate whether or not the user switched to dark mode, and dynamically set the icon based on it.
let isDarkMode = document.documentElement.classList.contains("dark-mode");
function myfunction(e) {
document.documentElement.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
e.currentTarget.querySelector("img").src = isDarkMode ? "sun.png" : "moon.png";
}
You can use the below reference for the toggle button from light mode to dark mode and dark mode to light mode.
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-checkbox {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.toggle-slot {
position: relative;
height: 10em;
width: 20em;
border: 5px solid #e4e7ec;
border-radius: 10em;
background-color: white;
box-shadow: 0px 10px 25px #e4e7ec;
transition: background-color 250ms;
}
.toggle-checkbox:checked ~ .toggle-slot {
background-color: #374151;
}
.toggle-button {
transform: translate(11.75em, 1.75em);
position: absolute;
height: 6.5em;
width: 6.5em;
border-radius: 50%;
background-color: #ffeccf;
box-shadow: inset 0px 0px 0px 0.75em #ffbb52;
transition: background-color 250ms, border-color 250ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .toggle-button {
background-color: #485367;
box-shadow: inset 0px 0px 0px 0.75em white;
transform: translate(1.75em, 1.75em);
}
.sun-icon {
position: absolute;
height: 6em;
width: 6em;
color: #ffbb52;
}
.sun-icon-wrapper {
position: absolute;
height: 6em;
width: 6em;
opacity: 1;
transform: translate(2em, 2em) rotate(15deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26,2,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .sun-icon-wrapper {
opacity: 0;
transform: translate(3em, 2em) rotate(0deg);
}
.moon-icon {
position: absolute;
height: 6em;
width: 6em;
color: white;
}
.moon-icon-wrapper {
position: absolute;
height: 6em;
width: 6em;
opacity: 0;
transform: translate(11em, 2em) rotate(0deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26,2.5,.46,.71);
}
.toggle-checkbox:checked ~ .toggle-slot .moon-icon-wrapper {
opacity: 1;
transform: translate(12em, 2em) rotate(-15deg);
}
<head>
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"> </script>
</head>
<label>
<input class='toggle-checkbox' type='checkbox'>
<div class='toggle-slot'>
<div class='sun-icon-wrapper'>
<div class="iconify sun-icon" data-icon="feather-sun" data-inline="false"></div>
</div>
<div class='toggle-button'></div>
<div class='moon-icon-wrapper'>
<div class="iconify moon-icon" data-icon="feather-moon" data-inline="false"></div>
</div>
</div>
</label>
function myfunction(e) {
const doc = document.documentElement
doc.classList.toggle("dark-mode");
document.querySelectorAll(".inverted").forEach((result) => {
result.classList.toggle("invert");
});
const img = e.currentTarget.querySelector('img')
const label = e.currentTarget.querySelector('span')
if (doc.classList.contains('dark-mode')) {
img.src = 'sun.png'
label.innerHTML = 'Light mode'
} else {
img.src = 'moon.png'
label.innerHTML = 'Dark mode'
}
}
const btn = document.querySelector('.btn')
btn.addEventListener('click', myfunction);
.dark-mode {
filter: invert(1) hue-rotate(180deg);
}
.invert {
filter: invert(1) hue-rotate(180deg);
}
'
<button class="btn">
<img src='moon.png' alt="" />
<span>Dark mode</span>
</button>

How to reset the progress bar after it ends

I've found this script for a progress bar. It runs smoothly from 100% to 0% after clicking the button.
But how can I reset the progress bar after it hits the 0? I'm planning to use this script in a slideshow and it should be 100% again after it reaches 0.
Hope you can help me in the right direction.
Thnx,
Leon
document.querySelector("button").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>
You can reset the progress bar by removing the complete class.
document.querySelector("button.start").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
document.querySelector("button.reset").addEventListener("click", () => {
let className = document.querySelector(".progress").className;
if (className.indexOf(' complete') > -1) {
className = className.substr(0, className.indexOf(' complete'));
document.querySelector(".progress .bar").style.transitionDuration = "0s";
document.querySelector(".progress").className = className;
}
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button class="start">
Start
</button>
<button class="reset">
Reset
</button>
If you want this to be dynamic without any user interaction, you can use setTimeout, set to the same duration used in your animationDuration, within your click event Handler to reset the transition and remove the complete class.
const start = document.querySelector("#start")
const progressBar = document.querySelector(".progress .bar")
const progress = document.querySelector(".progress")
function resetProgressBar(){
progressBar.style.transitionDuration = "10s"
progress.classList.add("complete")
setTimeout(() => {
progress.classList.remove("complete")// <-- remove the class with width:0
progressBar.style.transitionDuration = "0.1s" //<-- Add a very small transitionDuration or none if you prefer
}, 10000)// <-- Set this timeout duration to the same as your transitionDuration
}
start.addEventListener("click", resetProgressBar);
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button id="start">
Start
</button>
For selector .progress .bar, use a listener for event transitionend, because you are using transition rules in css:
The transitionend event is fired when a CSS transition has completed.
Inside this event listener, set transitionDuration to the default value. And in the next step, remove class complete from .progress, which will return the previous width of the progress bar.
document.querySelector("button").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
document.querySelector(".progress .bar").addEventListener("transitionend", () => {
document.querySelector(".progress.complete .bar").style.transitionDuration = "";
document.querySelector(".progress").classList.remove("complete");
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>
Thnx for all your suggestions. I've implemented the last suggestion into my slideshow script.
It is running and it is refreshing after slide change. But somewhere on the ride it stops, I think it's confused when to start the progress bar.
Anyone an idea to make this more solid?
jQuery('.owl-carousel').owlCarousel({
items: 1,
margin: 0,
nav: false,
dots: false,
slideBy: 1,
rewind: false,
autoplay: true,
autoplayTimeout: 5000,
autoplaySpeed: 10000,
loop: true,
animateOut: 'fadeOut',
responsive: false,
mouseDrag: false,
touchDrag: false,
lazyLoadEager: 2
});
jQuery('.owl-carousel').on('changed.owl.carousel', function(event) {
document.querySelector(".progress .bar").style.transitionDuration = "5s";
document.querySelector(".progress").className += " complete";
})
document.querySelector(".progress .bar").addEventListener("transitionend", () => {
document.querySelector(".progress.complete .bar").style.transitionDuration = "5";
document.querySelector(".progress").classList.remove("complete");
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>

How to highlight checkbox pseudo element which substitutes hidden checkbox input, when it required

I have a checkbox pseudo element which substitutes hidden checkbox input, and I want to highlight it when hidden checkbox is required. Is there any css solution? What is the right way to solve problem?
https://jsfiddle.net/ht2c9sbd/3/
<form>
<div class="group-input">
<input type="checkbox" id="c_1" required hidden="">
<label for="c_1">I agree with everything.</label>
</div>
<input type="submit" value="submit" id="submit">
</form>
css:
input[type="checkbox"] + label::before {
content: "";
display: inline-block;
height: 30px;
width: 30px;
margin: 0 14px 0 0;
cursor: pointer;
vertical-align: middle;
position: absolute;
left: 0;
top: calc(50% - 15px);
-webkit-transition: background-color 0.25s ease, border-color 0.25s ease;
transition: background-color 0.25s ease, border-color 0.25s ease;
}
input[type="checkbox"] + label::before {
content: "";
display: inline-block;
height: 30px;
width: 30px;
border: 1px solid rgba(20, 61, 139, 0.2);
margin: 0 14px 0 0;
cursor: pointer;
vertical-align: middle;
position: relative;
top: -2px;
}
input[type="checkbox"]:checked + label::before {
background-image: url(https://image.flaticon.com/icons/png/512/447/447147.png);
background-size: 15px;
border: 1px solid rgba(20, 61, 139, 0);
background-color: rgba(20, 61, 139, 0.2);
background-position: center;
background-repeat: no-repeat;
}
here is my horrible js solution:
function checkCheckbox(e) {
if (!e.checked) {
$('head').append('<style id="invalidCheckbox">input[type="checkbox"] + label::before {border: 1px solid #ff3535; box-shadow: 0 0 2px #ff3535;}</style>');
return false;
} else {
let temp = $('head style:last');
while(temp && temp[0].id === "invalidCheckbox"){
temp.remove();
temp = $('head style:last');
}
return true;
}
}
const mycheckbox = document.getElementById('c_1');
document.getElementById("submit").addEventListener('click', function(e){
e.preventDefault();
checkCheckbox(mycheckbox);
});
Script:
const mycheckbox = document.getElementById('c_1');
document.getElementById("submit").addEventListener('click', function(e){
e.preventDefault();
if ( mycheckbox.hasAttribute('required') && !mycheckbox.checked ){
mycheckbox.className = "invalid-input"; // <-- add new class
} else {
mycheckbox.className = "";
}
});
Style (you can change style as you want) :
input[type="checkbox"].invalid-input + label::before{
background-color: red;
}
Try to update the existing DOM element instead of creating a new element in DOM because DOM manipulation is slow.
A repaint occurs when changes are made to an elements skin that changes visibly but does not affect its layout.
Create new DOM element is even more critical to performance because it involves changes that affect the layout of a portion of the page
To highlight a required checkbox:
In CSS:
/* Style (any) element with the required attribute: */
:required {
background: red;
}
/* Style input elements with the required attribute: */
input:required {
box-shadow: 1px 1px 10px rgba(200, 0, 0, 0.5);
}
/* Style focused and required element: */
input:required:focus {
border: 2px solid red;
outline: none;
}
/* Style hover and required element: */
input:required:hover {
opacity: 1;
}
In JS:
// Get the element
var req = document.getElementById("my_checkbox").required;
// Check if required
if(req.hasAttribute('required')) {
// Style it
req.style.background = "red";
}

Categories