Change CSS translate3d() X/tx position - css

How to change CSS tx position from translate3d(tx, ty, tz)? The transform translate property comes from JavaScript. I don't want to change other property. I need to change only the tx position.
CSS Code: transform: translate3d(93px, -185px, 0) or transform: translate3d(58px, -130px, 0). It should replace to transform: translate3d(0, -185px, 0) or transform: translate3d(0, -130px, 0).

You should maintain the values ​​of ty tz in the following way:
const getTransform = el => {
var transform = window.getComputedStyle(el, null).getPropertyValue('-webkit-transform');
var results = transform.match(/matrix(?:(3d)\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))(?:, (-{0,1}\d+)), -{0,1}\d+\)|\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))\))/);
if (!results) return [0, 0, 0];
if (results[1] == '3d') return results.slice(2, 5);
results.push(0);
return results.slice(5, 8);
}
document.querySelector(".button").addEventListener("click", () => { // some event
const myEl = document.querySelector(".myElement"); //element to change transform tx
const [x, y, z] = getTransform(myEl);
myEl.style.transform = `translate3d(0, ${y}, ${z})`; // change to 0 only tx
});

Related

slideshow stuck framer motion

I want to make a slide show in framer motion and I found that in framer motion docs they have an example slide show like this https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed=&file=/src/Example.tsx, but I found a bug when we drag and double click it, it will be stuck like this picture .
import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import { images } from "./image-data";
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 1000 : -1000,
opacity: 0
};
},
center: {
zIndex: 1,
x: 0,
opacity: 1
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0
};
}
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
return Math.abs(offset) * velocity;
};
export const Example = () => {
const [[page, direction], setPage] = useState([0, 0]);images.
const imageIndex = wrap(0, images.length, page);
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection]);
};
return (
<>
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={page}
src={images[imageIndex]}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.2 }
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(1);
} else if (swipe > swipeConfidenceThreshold) {
paginate(-1);
}
}}
/>
</AnimatePresence>
</>
);
};
I try to solve this problem but still can't fix it, can someone help me?
This looks like a bug of framer-motion.
Up until v1.6.2, everything works fine. The bug seems to occur in all later versions.
There is also an interesting changelog:
[1.6.3] 2019-08-19
Fixed
Ensuring onDragEnd always fires after if onDragStart fired.
Here is a link to the related issue on GitHub, opened by the author of this question.
Until that bug is fixed, here is a workaround that uses Pan events
export default function Carousel() {
const animationConfidenceThreshold = 200; // you have to move the element 200px in order to perform an animation
const [displayed, setDisplayed] = useState(0); // the index of the displayed element
const xOffset = useMotionValue(0); // this is the motion value that drives the translation
const lastOffset = useRef(0); // this is the lastValue of the xOffset after the Pan ended
const elementAnimatingIn = useRef(false); // this will be set to true whilst a new element is performing its animation to the center
useEffect(() => {
// this happens after we have dragged the element out and triggered a rerender
if (elementAnimatingIn.current) {
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
// if the element has animated out to the right it animates in from the left
xOffset.set(
rightPan ? -1 * window.innerWidth - 200 : window.innerWidth + 200
);
// perform the animation to the center
animate(xOffset, 0, {
duration: 0.5,
onComplete: () => {
xOffset.stop();
},
onStop: () => {
elementAnimatingIn.current = false;
lastOffset.current = xOffset.get();
}
});
}
});
return (
<div className="container">
<motion.div
className="carouselElement"
onPan={(e, info) => {
xOffset.set(lastOffset.current + info.offset.x); // set the xOffset to the current offset of the pan + the prev offset
}}
style={{ x: xOffset }}
onPanStart={() => {
// check if xOffset is animating, if true stop animation and set lastOffset to current xOffset
if (xOffset.isAnimating()) {
xOffset.stop();
lastOffset.current = xOffset.get();
}
}}
onPanEnd={(e, info) => {
// there can be a difference between the info.offset.x in onPan and onPanEnd
// so we will set the xOffset to the info.offset.x when the pan ends
xOffset.set(lastOffset.current + info.offset.x);
lastOffset.current = xOffset.get(); // set the lastOffset to the current xOffset
if (Math.abs(lastOffset.current) < animationConfidenceThreshold) {
// if its only a small movement, animate back to the initial position
animate(xOffset, 0, {
onComplete: () => {
lastOffset.current = 0;
}
});
} else {
// perform the animation to the next element
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
animate(
xOffset,
rightPan ? window.innerWidth + 200 : -1 * window.innerWidth - 200, // animate out of view
{
duration: 0.5,
onComplete: () => {
// after the element has animated out
// stop animation (it does not do this on its own, only one animation can happen at a time)
xOffset.stop();
elementAnimatingIn.current = true;
// trigger a rerender with the new content - now the useEffect runs
setDisplayed(rightPan ? displayed - 1 : displayed + 1);
}
}
);
}
}}
>
<span style={{ userSelect: "none" }}>
{"I am element #" + displayed}
</span>
</motion.div>
</div>
);
}
Check this codesandbox out!

CSS: Set max-height to same value as height becomes when "none" max-height?

So I am making a dropdown that uses transition of max-height and opacity, for example if the dropdown was 500 pixels in height, I would use 500px for max-height when open, and 0 when closed. However, my dropdown's height is auto calculated. I need to set max-height to this auto calculated height, so that the transition has correct timin. Otherwise, if I used a larger max-height value, when being closed the dropdown would not move until max-height had transitioned below the true value, and then suddenly move faster for the remaining time. Any way to do this?
So I found a solution. I just set the maxHeight to the offsetHeight before triggering closing of my dropdown.
I also needed to use setTimeout of 0 milliseconds for triggering closing it though after running the below function.
export function retreatDropdown(e: HTMLElement) {
e.style.maxHeight = `${e.offsetHeight}px`;
}
I could do a similar trick for opening animation, but you won't know the correct maxHeight until it is opened, so my guess this is more tricky (but less important than closing animation IMO).
EDIT: Complete (TypeScript) solution for both closing/opening dropdown
const defaultTransitionTime = 200;
export function toggleDropdown(
e: HTMLElement,
value: boolean = null,
toggleAction: () => void = null // Function that toggles true/false for accordion open
) {
if (value) {
openDropdown(e, toggleAction);
} else {
retreatDropdown(e, toggleAction);
}
}
export function openDropdown(
e: HTMLElement,
openAction: () => void = null
) {
if (openAction) setTimeout(() => openAction(), 0);
e.style.display = "block";
setTimeout(() => {
const offsetHeight = e.offsetHeight;
e.style.maxHeight = "0px";
setTimeout(() => {
e.style.maxHeight = `${offsetHeight}px`;
}, 5);
}, 5);
setTimeout(() => {
e.style.maxHeight = "none";
}, defaultTransitionTime);
}
export function retreatDropdown(
e: HTMLElement,
closeAction: () => void = null
) {
e.style.maxHeight = `${e.offsetHeight}px`;
if (closeAction) setTimeout(() => closeAction(), 0);
}
Dropdown CSS class:
.dropdown {
transition: opacity 200ms linear, max-height 200ms linear;
will-change: opacity, max-height;
}
Angular ngStyle (applied on open/close of accordion):
elementStyle(e: HTMLElement) {
return this.isOpen(HTMLElement) // I use a map of booleans for open state here...
? { display: "block", "max-height": "none" }
: { "max-height": 0, opacity: 0 };
}

Can we restrict CSS keyframe animations to a scope

I wonder if it's possible to restrict keyframe animations to a scope based on classnames. The benefit would be to be able to use the same animation-name multiple times without getting issues. I couldn't find any infos about that..
In case it's not possible:
are there any best practices to handle naming conflicts?
I used to use something like SCSS to generate automatically created names for my keyframes. They might not be as descriptive, but they ensure uniqueness. Something like:
$animation-id-count: 0 !global;
#function animation-id {
$animation-id-count: $animation-id-count + 1;
#return animation-id-#{$animation-id-count};
}
After this, just use the function in your code like this:
.class {
$id: animation-id();
#keyframes #{$id}{
...keyframes
}
animation: $id 1s infinite;
}
That way if you insert it anywhere else in your SCSS or move it, it will still match the right animation, and it stops namespaces from overlapping in any way.
Here is a JSX approach (you will need object-hash for that).
The following example shows how to define different animations with respective unique ID based on transform: scale(n). For that purpose, define a function which returns the keyframes and its ID. The keyframes ID is a custom string suffixed with a hash of the function options (e.g. the scale factor).
(Be careful of CSS custom identifier, e.g. do not include a . in your ID. See MDN: < custom-ident >.)
import hash from "object-hash";
const keyFramesScale = (options = {}) => {
let { transforms, id, scale } = options;
transforms = transforms || "";
scale = scale || 1.25;
const keyFramesId = `scale${id ? "-" + id : ""}-${hash(options).substring(0, 6)}`;
const keyFrames = {
[`#keyframes ${keyFramesId}`]: {
"100%": {
transform: `scale(${scale}) ${transforms}`,
},
"0%": {
transform: `scale(1) ${transforms}`,
}
}
};
return [keyFramesId, keyFrames];
};
How to use it:
const [scaleUpId, keyFramesScaleUp] = keyFramesScale({ scale: 1.25, transforms: "rotate(-30deg)", id: "up" });
const [scaleDownId, keyFramesScaleDown] = keyFramesScale({ scale: 0.75, transforms: "rotate(-30deg)", id: "down" });
// scaleUpId = "scale-up-c61254"
// scaleDownId = "scale-down-6194d5"
// ...
<tag style={{
...keyFramesScaleUp,
...keyFramesScaleDown,
...(!hasTouchScreen && isActive && !isClicked && {
animation: `${scaleUpId} 0.5s infinite alternate linear`,
"&:hover": {
animation: "none",
},
}),
...(isClicked && {
animation: `${scaleDownId} .25s 1 linear`,
}),
}} />
Of course, you can write a more generic function that hashes the whole key frames and assign it an ID based on that.
EDIT
To concretize what has been said, here is the generic approach. We first define a generic function that takes an animation name (e.g. scale, pulse, etc.), its keyframes (which can be an object or a function), and optionally keyframes parameters and its default values.
import hash from "object-hash";
const createKeyFramesId = (id, keyFrames) => {
return `${id}-${hash(keyFrames).substring(0, 6)}`;
};
const genericKeyFrames = (name, keyFrames, defaults = {}, options = {}) => {
if (typeof keyFrames === "function") {
// The order of defaults & options is important: the latter overrides the former.
keyFrames = keyFrames({ ...defaults, ...options });
}
const keyFramesId = createKeyFramesId(name, keyFrames);
const keyFramesObject = {
[`#keyframes ${keyFramesId}`]: keyFrames
};
return [keyFramesId, keyFramesObject];
};
From now on, we can define all kind of animations. Their usage is the same as above.
export const keyFramesPulse = () =>
genericKeyFrames("pulse", {
"100%": {
opacity: "1",
},
"0%": {
opacity: "0.5",
},
});
export const keyFramesRotate = (options = {}) => {
const defaults = {
rotate: 360,
transforms: "",
};
const rotateKeyFrames = ({ rotate, transforms }) => {
return {
"100%": {
transform: `rotate(${rotate}deg) ${transforms}`,
}
}
};
return genericKeyFrames(`rotate`, rotateKeyFrames, defaults, options);
};
export const keyFramesScale = (options = {}) => {
const defaults = {
scale: 1.25,
transforms: ""
};
const scaleKeyFrames = ({ scale, transforms }) => {
return {
"100%": {
transform: `scale(${scale}) ${transforms}`,
},
"0%": {
transform: `scale(1) ${transforms}`,
}
}
};
return genericKeyFrames(`scale`, scaleKeyFrames, defaults, options);
};
What it looks like in DevTools:

Why I've got these white borders with chrome with some resolutions and how to remove them?

I'm playing with CSS rotatations to get a kaleidoscope effect in CSS.
Everything seems good except with Chrome at some resolution (no issue on IE10, I didn't test FF)
I don't know why but sometime I have some weird white borders at the center of the kaleidoscope even if everything seems fine with the rotation values. I can't find any workaround...
You can test a working demo here : http://jsfiddle.net/Lvc0u55v/4519/
You'll probably need to move the slider from jsfiddle to see the white borders displayed.
I'm using this kind of css: transform: rotate(151deg) matrix(-1, 0, 0, 1, 0, 0) with a background image.
Could you help me to remove these borders?
Hope this will help.
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', ['$scope', '$timeout',
function($scope, $timeout) {
$scope.bgImage = 'url(http://www.larousse.fr/encyclopedie/data/images/1310226-Vincent_Van_Gogh_la_Nuit_%C3%A9toil%C3%A9e.jpg)';
var section = 12;
$scope.getNumber = function() {
return new Array(section);
}
$scope.getRotation = function(i) {
var hasMatrix = false, deg = 0, base = 360 / section, rotation;
if (i % 2 === 0) {
i -= 1;
hasMatrix = true;
}
deg = Math.round(i * base + 1);
if (section <= 4) {
deg -= 1;
}
rotation = 'rotate(' + deg + 'deg)';
if (hasMatrix) {
// Please updated this line
rotation += ' matrix(-1, 0, 0, 1, -1, 0)';
}
return rotation;
}
$scope.mode = 'move';
$scope.onMousemove = function(e) {
console.log($scope.mode);
if ($scope.mode === 'move') {
$scope.bgPosition = e.pageX + 'px ' + e.pageY + 'px';
}
};
}]);

How can I test for clip-path support?

clip-path:shape() does not seem to work in IE (no surprise) and Firefox (a bit surprised). Is there a way to test for clip-path support? I use modernizr. (By the way, I know I can get this to work using SVGs and -webkit-clip-path:url(#mySVG))
You asked this a while ago, and to be honest, I'm not sure if Modernizr has yet to add support for this, but it's pretty easy to roll your own test in this case.
The steps are:
Create, but do not append, a DOM element.
Check that it supports any kind of clipPath by checking the JS style attribute of the newly created element (element.style.clipPath === '' if it can support it).
Check that it supports CSS clip path shapes by making element.style.clipPath equal some valid CSS clip path shape.
Of course, it's a little more complex than that, as you have to check for vendor-specific prefixes.
Here it is all together:
var areClipPathShapesSupported = function () {
var base = 'clipPath',
prefixes = [ 'webkit', 'moz', 'ms', 'o' ],
properties = [ base ],
testElement = document.createElement( 'testelement' ),
attribute = 'polygon(50% 0%, 0% 100%, 100% 100%)';
// Push the prefixed properties into the array of properties.
for ( var i = 0, l = prefixes.length; i < l; i++ ) {
var prefixedProperty = prefixes[i] + base.charAt( 0 ).toUpperCase() + base.slice( 1 ); // remember to capitalize!
properties.push( prefixedProperty );
}
// Interate over the properties and see if they pass two tests.
for ( var i = 0, l = properties.length; i < l; i++ ) {
var property = properties[i];
// First, they need to even support clip-path (IE <= 11 does not)...
if ( testElement.style[property] === '' ) {
// Second, we need to see what happens when we try to create a CSS shape...
testElement.style[property] = attribute;
if ( testElement.style[property] !== '' ) {
return true;
}
}
}
return false;
};
Here's a codepen proof-of-concept: http://codepen.io/anon/pen/YXyyMJ
You can test with Modernizr.
(function (Modernizr) {
// Here are all the values we will test. If you want to use just one or two, comment out the lines of test you don't need.
var tests = [{
name: 'svg',
value: 'url(#test)'
}, // False positive in IE, supports SVG clip-path, but not on HTML element
{
name: 'inset',
value: 'inset(10px 20px 30px 40px)'
}, {
name: 'circle',
value: 'circle(60px at center)'
}, {
name: 'ellipse',
value: 'ellipse(50% 50% at 50% 50%)'
}, {
name: 'polygon',
value: 'polygon(50% 0%, 0% 100%, 100% 100%)'
}
];
var t = 0, name, value, prop;
for (; t < tests.length; t++) {
name = tests[t].name;
value = tests[t].value;
Modernizr.addTest('cssclippath' + name, function () {
// Try using window.CSS.supports
if ('CSS' in window && 'supports' in window.CSS) {
for (var i = 0; i < Modernizr._prefixes.length; i++) {
prop = Modernizr._prefixes[i] + 'clip-path'
if (window.CSS.supports(prop, value)) {
return true;
}
}
return false;
}
// Otherwise, use Modernizr.testStyles and examine the property manually
return Modernizr.testStyles('#modernizr { ' + Modernizr._prefixes.join('clip-path:' + value + '; ') + ' }', function (elem, rule) {
var style = getComputedStyle(elem),
clip = style.clipPath;
if (!clip || clip == "none") {
clip = false;
for (var i = 0; i < Modernizr._domPrefixes.length; i++) {
test = Modernizr._domPrefixes[i] + 'ClipPath';
if (style[test] && style[test] !== "none") {
clip = true;
break;
}
}
}
return Modernizr.testProp('clipPath') && clip;
});
});
}
})(Modernizr);
Check this codepen to see it in action.

Resources