Please help me figure it out. There is a text animation effect on click on the screen, it is written in "tween.js". Does anyone know how to create such an effect on "tween.js"?
Here is the link: https://michalzalobny.com/orbit-gallery
I tried to read the "tween.js" documentation and looked at the examples, but nothing like that is written there.
Not sure about Tween.JS, but here's how to do it using vanilla JavaScript in order to toggle a class, and let CSS handle the rest.
The staggered characters animation effect is made possible by assigning a CSS var --anim-delay to each character SPAN with a value of the respective SPAN index, later used in CSS as the transition-delay:
// DOM helper functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Staggered characters flip effect:
const elText = el("#text");
const words = ["Discover", "Swipe", "Culture"];
// Create SPANs for words and charactres
words.forEach(word => {
const elsChars = [...word].map((char, i) => {
const elChar = elNew("span", { className: "char", textContent: char });
elChar.style.setProperty("--anim-delay", i);
return elChar;
});
const elWord = elNew("span", { className: "word" });
elWord.append(...elsChars);
elText.append(elWord);
});
// Animate on pointer events:
el("body").addEventListener("pointerdown", () => {
elText.classList.add("is-pressed");
});
el("body").addEventListener("pointerup", () => {
elText.classList.remove("is-pressed");
});
#text {
display: flex;
justify-content: center;
gap: 2rem;
-webkit-font-smoothing: antialiased;
font-smoothing: antialiased;
perspective: 600px;
}
.word {
font-size: 9vw;
display: inline-flex;
pointer-events: none;
user-select: none;
}
.char {
transition:
transform 0.3s calc(var(--anim-delay) * 20ms) ease-in-out,
opacity 0.3s calc(var(--anim-delay) * 20ms) ease-in-out;
opacity: 1;
transform-origin: center center 0.4em;
}
.is-pressed .char {
transform: rotate3d(-1, -0.4, 0, 90deg);
opacity: 0;
}
(Click and hold)
<div id="text"></div>
Related
I'm trying to come up with a cross-device code that handles pointer events.
I'm running this code successfully on Chrome/Android (using USB debugging), but the Chrome desktop just acts up and keeps firing pointermove after the mouse has been released.
(Another problem is that the moves are not as smooth as on the mobile)
Playable demo
These SO posts don't solve my problem:
event-listener-with-pointerup-not-firing-when-activated-from-touchscreen
pointerup-event-does-not-fire-for-mouse-actions-on-a-link-when-pointermove-has-b
The "pointerup" Event is assigned to the #canvas, but such event will never occur because the mouse is actually above the generated DIV circle.
Since your circles are just visual helpers, set in CSS
.dot {
/* ... */
pointer-events: none;
}
Also, make sure to use Event.preventDefault() on "pointerdown".
Regarding the other strategies for a seamless experience, both on desktop and on mobile (touch):
assign only the "pointerdown" Event to a desired Element (canvas in your case)
use the window object for all the other events
Edited example:
const canvas = document.getElementById('canvas');
function startTouch(ev) {
ev.preventDefault();
const dot = document.createElement('div');
dot.classList.add('dot');
dot.id = ev.pointerId;
dot.style.left = `${ev.pageX}px`;
dot.style.top = `${ev.pageY}px`;
document.body.append(dot);
}
function moveTouch(ev) {
const dot = document.getElementById(ev.pointerId);
if (!dot) return;
dot.style.left = `${ev.pageX}px`;
dot.style.top = `${ev.pageY}px`;
}
function endTouch(ev) {
const dot = document.getElementById(ev.pointerId);
if (!dot) return;
removeDot(dot);
}
function removeDot(dot) {
dot.remove();
}
canvas.addEventListener('pointerdown', startTouch);
addEventListener('pointermove', moveTouch);
addEventListener('pointerup', endTouch);
addEventListener('pointercancel', endTouch);
.dot {
background-color: deeppink;
width: 2rem;
height: 2rem;
border-radius: 50%;
transform: translate(-50%, -50%);
position: absolute;
pointer-events: none; /* ADD THIS! */
}
#canvas {
height: 50vh;
background-color: black;
touch-action: none;
}
body {
margin: 0;
}
<div id="canvas"></div>
The code needs also this improvement:
Don't query the DOM inside a pointermove event
Using CSS vars
As per the comments section here's a viable solution that uses custom properties CSS variables and JS's CSSStyleDeclaration.setProperty() method.
Basically the --x and --y CSS properties values are updated from the pointerdown/move event handlers to reflect the current clientX and clientY values:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
const canvas = document.getElementById('canvas');
const pointersDots = (parent) => {
const elParent = typeof parent === "string" ? el(parent) : parent;
const dots = new Map();
const moveDot = (elDot, {clientX: x,clientY: y}) => {
elDot.style.setProperty("--x", x);
elDot.style.setProperty("--y", y);
};
const onDown = (ev) => {
ev.preventDefault();
const elDot = elNew("div", { className: "dot" });
moveDot(elDot, ev);
elParent.append(elDot);
dots.set(ev.pointerId, elDot);
};
const onMove = (ev) => {
if (dots.size === 0) return;
const elDot = dots.get(ev.pointerId);
moveDot(elDot, ev);
};
const onUp = (ev) => {
if (dots.size === 0) return;
const elDot = dots.get(ev.pointerId);
elDot.remove();
dots.delete(ev.pointerId);
};
canvas.addEventListener('pointerdown', onDown);
addEventListener('pointermove', onMove);
addEventListener('pointerup', onUp);
addEventListener('pointercancel', onUp);
};
// Init: Pointers helpers
pointersDots("#canvas");
* {
margin: 0;
}
.dot {
--x: 0;
--y: 0;
pointer-events: none;
position: absolute;
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: deeppink;
transform: translate(-50%, -50%);
left: calc(var(--x) * 1px);
top: calc(var(--y) * 1px);
}
#canvas {
margin: 10vh;
height: 80vh;
background-color: black;
touch-action: none;
}
<div id="canvas"></div>
Is it possible to a piece of javascript on mobile/tablet devices only? I'd like to do something a bit better than display: none and it makes sense to stop the script from running if it's not required?
Basically I have a custom cursor effect, that is only required when it follows the cursor on desktop with a mouse/trackpad.
This is the script I have:
var cursor = document.querySelector('.cursor-outer');
var cursorinner = document.querySelector('.cursor');
var a = document.querySelectorAll('a');
var moveCursor = true;
var radiusOfCursor = parseInt(getComputedStyle(cursor).getPropertyValue('width')) / 2; // radiusOfCursor = (width_of_cursor / 2).
document.addEventListener('mousemove', function (e) {
var x = e.clientX;
var y = e.clientY;
cursorinner.style.left = x + 'px';
cursorinner.style.top = y + 'px';
if (!moveCursor) return;
cursor.style.marginLeft = `calc(${e.clientX}px - ${radiusOfCursor}px)`;
cursor.style.marginTop = `calc(${e.clientY}px - ${radiusOfCursor}px)`;
moveCursor = false;
setTimeout(() => {
moveCursor = true;
}, 32) // The wait time. I chose 95 because it seems to work just fine for me.
});
/* Centre pointer after stopping */
function mouseMoveEnd() {
cursor.style.marginLeft = `calc(${cursorinner.style.left} - ${radiusOfCursor}px)`;
cursor.style.marginTop = `calc(${cursorinner.style.top} - ${radiusOfCursor}px)`;
}
var x;
document.addEventListener('mousemove', function() {
if (x) clearTimeout(x);
x = setTimeout(mouseMoveEnd, 10);
}, false);
/* End */
document.addEventListener('mousedown', function() {
cursor.classList.add('click');
cursorinner.classList.add('cursorinnerhover');
});
document.addEventListener('mouseup', function() {
cursor.classList.remove('click');
cursorinner.classList.remove('cursorinnerhover');
});
a.forEach(item => {
item.addEventListener('mouseover', () => {
cursor.classList.add('hover');
});
item.addEventListener('mouseleave', () => {
cursor.classList.remove('hover');
});
});
a.forEach((item) => {
const interaction = item.dataset.interaction;
item.addEventListener("mouseover", () => {
cursorinner.classList.add(interaction);
});
item.addEventListener("mouseleave", () => {
cursorinner.classList.remove(interaction);
});
});
* {
cursor: none;
}
.cursor-outer {
border: 2px solid black;
border-radius: 100%;
box-sizing: border-box;
height: 32px;
pointer-events: none;
position: fixed;
top: 16px;
left: 16px;
transform: translate(-50%, -50%);
transition: height .12s ease-out, margin .12s ease-out, opacity .12s ease-out, width .12s ease-out;
width: 32px;
z-index: 100;
}
.cursor {
background-color: black;
border-radius: 100%;
height: 4px;
opacity: 1;
position: fixed;
transform: translate(-50%, -50%);
pointer-events: none;
transition: height .12s, opacity .12s, width .12s;
width: 4px;
z-index: 100;
}
<div class="cursor-outer"></div>
<div class="cursor"></div>
Thanks in advance!
Option #1
You can use something similar to this to determine if a device is touch-enabled:
isTouchDevice = () => {
return ( 'ontouchstart' in window ) ||
( navigator.maxTouchPoints > 0 ) ||
( navigator.msMaxTouchPoints > 0 );
};
This is adapted from Patrick H. Lauke's Detecting touch article on Mozilla.
Then just: if (isTouchDevice()) { /* Do touch screen stuff */}
Option #2
But maybe a pure CSS approach could work better in your situation, like:
#media (hover: none) {
.cursor {
pointer-events: none;
}
}
Option #3
If you don't mind using a third-party library, then Modernizr is really great for detecting things like this in the user's environment. Specifically, Modernizr.pointerevents will confirm if touchscreen is being used.
Vision: On "mouseenter" spin the letter and change the color. On second "mouseenter" spin the letter and change color back.
Problem: When changing class, second animation is not working.
const landingEl = document.getElementById("landing");
const landingText = document.getElementById("landing-text");
// line brake on dot
const landingFraze = `Lorem ipsum dolor sit amet consectetur adipisicing elit.`;
const spans = document.getElementsByTagName('span');
let letterList = [];
let text;
// rotate leter function
function rotate(ind) {
let index = ind.path[0].id
let spiningLeter = document.getElementById(index);
if(!spiningLeter.classList.contains ('rotate') & !spiningLeter.classList.contains('unrotate')) {
spiningLeter.classList.add('rotate');
}
else if (spiningLeter.classList.contains('unrotate')) {
spiningLeter.classList.remove('unrotate');
spiningLeter.classList.add('rotate');
}
else {
spiningLeter.classList.replace('rotate', 'unrotate');
}
}
function everyLeter() {
let text = landingFraze;
// split fraze in sentences on dot
let sentenceList = text.split(".");
// adding css to every letter
sentenceList.forEach((element, i) => {
let div = document.createElement('div');
// spliting every letter
let list = element.split("");
// creating new element and adding letter to it with css
list.forEach((leter, index) => {
let span = document.createElement("span");
span.id = `${index}a${i}`;
// event listener on mouse enter
span.addEventListener('mouseenter', rotate);
let leterP = document.createElement('p');
// adding space between words
if(leter === ' ') {
leterP.style.marginLeft = '20px'
}
// add a dot at the end of the sentence
if(list.length === index + 1){
leterP.textContent = `${leter}.`;
} else {
leterP.textContent = leter;
}
// appending
span.appendChild(leterP);
div.appendChild(span);
});
// appending div to landing
landingText.appendChild(div);
});
}
// functions
everyLeter();
#import url("https://fonts.googleapis.com/css2?family=Luckiest+Guy&family=Roboto:wght#400;700&display=swap");
:root {
--font-color: rgb(243, 14, 224);
}
.landing-text {
font-family: "Luckiest Guy", cursive;
flex-wrap: wrap;
height: 100vh;
font-size: 6vw;
margin-top: 40vh;
margin-left: 4rem;
}
.landing-text div{
display: flex;
max-height: 120px;
}
.rotate {
animation: rotate 2s forwards;
}
#keyframes rotate {
100% {
transform: rotateY(720deg);
color: var(--font-color);
}
};
.unrotate {
animation: unrotate 2s forwards;
}
#keyframes unrotate {
100%{
transform: rotateY(720deg);
color: black;
};
}
<div class="landing" id='landing'>
<div class ="landing-text" id="landing-text"></div>
</div>
Vision: On "mouseenter" spin the letter and change the color. On second "mouseenter" spin the letter and change color back.
Problem: When changing class, second animation is not working.
Javscript
function rotate(ind) {
let index = ind.path[0].id;
let spiningLeter = document.getElementById(index);
if(!spiningLeter.classList.contains ('rotate') & !spiningLeter.classList.contains('unrotate')) {
spiningLeter.classList.add('rotate');
}
else if (spiningLeter.classList.contains('unrotate')) {
spiningLeter.classList.remove('unrotate');
spiningLeter.classList.add('rotate');
}
else {
spiningLeter.classList.replace('rotate', 'unrotate');
}
}
CSS
.rotate {
animation: rotate 2s forwards;
}
#keyframes rotate {
100% {
transform: rotateY(360deg);
color: rgb(26, 212, 2)}
};
.unrotate {
animation: unrotate 2s forwards;
}
#keyframes unrotate {
100%{
transform: rotateY(720deg);
color: var(--font-color);
};
}
Try and specify the start state of each animation.
const landingEl = document.getElementById('landing')
const landingText = document.getElementById('landing-text')
// line brake on dot
const landingFraze = `Lorem ipsum dolor sit amet consectetur adipisicing elit.`
const spans = document.getElementsByTagName('span')
let letterList = []
let text
// rotate leter function
function rotate(ind) {
let spiningLeter = ind.target
if (!spiningLeter.classList.contains('rotate') &
!spiningLeter.classList.contains('unrotate')
) {
spiningLeter.classList.add('rotate')
} else if (spiningLeter.classList.contains('unrotate')) {
spiningLeter.classList.remove('unrotate')
spiningLeter.classList.add('rotate')
} else {
spiningLeter.classList.replace('rotate', 'unrotate')
}
}
function everyLeter() {
let text = landingFraze
// split fraze in sentences on dot
let sentenceList = text.split('.')
// adding css to every letter
sentenceList.forEach((element, i) => {
let div = document.createElement('div')
// spliting every letter
let list = element.split('')
// creating new element and adding letter to it with css
list.forEach((leter, index) => {
let span = document.createElement('span')
span.id = `${index}a${i}`
// event listener on mouse enter
span.addEventListener('mouseenter', rotate)
let leterP = document.createElement('p')
// adding space between words
if (leter === ' ') {
leterP.style.marginLeft = '20px'
}
// add a dot at the end of the sentence
if (list.length === index + 1) {
leterP.textContent = `${leter}.`
} else {
leterP.textContent = leter
}
// appending
span.appendChild(leterP)
div.appendChild(span)
})
// appending div to landing
landingText.appendChild(div)
})
}
// functions
everyLeter()
:root {
--font-color: rgb(243, 14, 224);
}
.landing-text {
font-family: 'Luckiest Guy', cursive;
font-size: 4vw;
}
.landing-text div {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.rotate {
animation: rotate 2s forwards;
}
#keyframes rotate {
0% {
color: black;
transform: rotateY(0deg);
}
100% {
transform: rotateY(720deg);
color: var(--font-color);
}
}
.unrotate {
animation: unrotate 2s forwards;
}
#keyframes unrotate {
0% {
color: var(--font-color);
transform: rotateY(720deg);
}
100% {
transform: rotateY(0deg);
color: black;
}
}
/* For demo only */
span {
margin: 4px;
padding: 0;
line-height: 0px;
}
/* ***** */
<div class="landing" id="landing">
<div class="landing-text" id="landing-text"></div>
</div>
This codepen shows my problem: http://codepen.io/PiotrBerebecki/pen/pNvpdG
When the user clicks on the big button the css opacity is reduced to 0. Since I've applied the following rule: transition: opacity 0.5s ease-in-out; the fade out animation is smooth.
I would like to achieve the same smooth transition when the next button fades in.
However for some reason the next button appears suddenly without any transition.
Would you know what causes the issue and how to fix it?
console.clear();
(function() {
// Data for the app
const model = {
buttons: ['tomato', 'blue'],
currentButton: -1
};
// Logic for the app
const controller = {
init: function() {
view.init();
},
getButtonName: function() {
model.currentButton = (model.currentButton + 1) % model.buttons.length;
return model.buttons[model.currentButton];
}
};
// View for the app
const view = {
init: function() {
this.root = document.getElementById('root');
this.showNext();
},
animationDelay: 500,
showNext: function() {
// Get next button name
const buttonName = controller.getButtonName();
// Create button DOM element
const buttonElement = document.createElement('div');
buttonElement.className = 'button';
buttonElement.id = buttonName;
buttonElement.textContent = buttonName;
buttonElement.style.opacity = 0;
// Add event listender for the button
buttonElement.addEventListener('click', event => {
// Reduce opacity
buttonElement.style.opacity = 0;
// Remove the button from DOM
setTimeout(() => {
this.root.removeChild(buttonElement);
}, this.animationDelay + 10);
// Start the function to show next button
setTimeout(() => {
this.showNext();
}, this.animationDelay + 20);
});
// Add button to DOM
this.root.appendChild(buttonElement);
// Show button by increasing opacity
buttonElement.style.opacity = 1;
}
};
// Start the app
controller.init();
}());
#tomato {
background: tomato;
}
#blue {
background: DeepSkyBlue;
}
.button {
transition: opacity 0.5s ease-in-out;
width: 100%;
height: 50vh;
border: solid 3px black;
cursor: pointer;
}
<div id="root"></div>
This should work , Code pen link: http://codepen.io/saa93/pen/gLbvmQ
You would need to add this instead of directly setting opacity to 1
// Show button by increasing opacity
buttonElement.style.opacity = 0;
setTimeout(() => {
buttonElement.style.opacity = 1;
}, this.animationDelay + 20);
Add a class (in the Snippet is .active) add the following:
CSS
.button {
opacity: 0;
transition: opacity 0.5s ease-in-out;
width: 100%;
height: 50vh;
border: solid 3px black;
cursor: pointer;
}
.button.active {
opacity: 1;
transition: opacity 0.5s ease-in-out;
}
JavaScript
...
// Reduce opacity
buttonElement.classList.toggle('active');
buttonElement.style.opacity = 0;
...
// Show button by increasing opacity
buttonElement.classList.toggle('active');
buttonElement.style.opacity = 1;
SNIPPET
console.clear();
(function() {
// Data for the app
const model = {
buttons: ['tomato', 'blue'],
currentButton: -1
};
// Logig for the app
const controller = {
init: function() {
view.init();
},
getButtonName: function() {
model.currentButton = (model.currentButton + 1) % model.buttons.length;
return model.buttons[model.currentButton];
}
};
// View for the app
const view = {
init: function() {
this.root = document.getElementById('root');
this.showNext();
},
animationDelay: 500,
showNext: function() {
// Get next button name
const buttonName = controller.getButtonName();
// Create button DOM element
const buttonElement = document.createElement('div');
buttonElement.className = 'button';
buttonElement.id = buttonName;
buttonElement.textContent = buttonName;
buttonElement.style.opacity = 0;
// Add event listender for the button
buttonElement.addEventListener('click', event => {
// Reduce opacity
buttonElement.classList.toggle('active');
buttonElement.style.opacity = 0;
// Remove the button from DOM
setTimeout(() => {
this.root.removeChild(buttonElement);
}, this.animationDelay + 10);
// Start the function to show next button
setTimeout(() => {
this.showNext();
}, this.animationDelay + 20);
});
// Add button to DOM
this.root.appendChild(buttonElement);
// Show button by increasing opacity
buttonElement.classList.toggle('active');
buttonElement.style.opacity = 1;
}
};
// Start the app
controller.init();
}());
#tomato {
background: tomato;
}
#blue {
background: DeepSkyBlue;
}
.button {
opacity: 0;
transition: opacity 0.5s ease-in-out;
width: 100%;
height: 50vh;
border: solid 3px black;
cursor: pointer;
}
.button.active {
opacity: 1;
transition: opacity 0.5s ease-in-out;
}
<div id="root"></div>
after this.root.appendChild(buttonElement);
you should set opacity to 0 and let the browser time to render before buttonElement.style.opacity = 1;
BTW I think removing and adding the element of not a good way to do this
.button {
width: 100%;
height: 50vh;
border: solid 3px black;
cursor: pointer;
animation-name: example;
animation-duration:3.5s;
}
#keyframes example {
0% {opacity:1}
50% {opacity:0}
100% {opacity:1}
}
What U really want is to use animation like this:JSFIDDLE EXAMPLE
This way the animation does all this timing and opacity back and forth using the css only
I am trying to fade an element that is being created dynamically with JavaScript.
Here's a CSS example of what I am trying to do: https://codepen.io/deejay/pen/OJJqZaL
Javascript
...
const fadeOutWithOpacity = () => {
const opacity = totalCount >= 1 && 0.5;
return opacity;
};
const handleOpacity = () => {
setInterval(() => {
fadeOutWithOpacity();
}, 3000);
};
...
style: {
background: "#FA4379",
color: "#fff",
opacity: `${handleOpacity()}`
}
I only want to change the opacity value every 3sec
You can use Javascript to add a class to your element which will cause the opacity to decrease slowly thanks to the CSS transition property.
const spawnBox = () => {
var box = document.createElement("div");
box.className = "box";
box.onclick = (event) => {
box.classList.add("hidden");
};
document.body.appendChild(box);
};
spawnBox();
setInterval(() => {
spawnBox();
}, 1000);
.box {
width: 20px;
height: 20px;
margin: 5px;
background: red;
transition: opacity .5s;
}
.box.hidden {
opacity: 0;
}