I have some code to slide out a menu which works:
Vue.set(this.filterStyles, filterIndex, {
display: "block",
height: "auto",
});
let filterValuesElHeight;
Vue.nextTick().then(() => {
let filterValuesEl = document.getElementById('parent-filter-values-' + filterIndex);
filterValuesElHeight = filterValuesEl.clientHeight;
Vue.set(this.filterStyles, filterIndex, {
height: 0,
display: "block"
});
return Vue.nextTick();
}).then(() => {
setTimeout(() => {
Vue.set(this.filterStyles, filterIndex, {
height: filterValuesElHeight + "px",
display: "block"
});
}, 10);
});
Initially the menu is setup with the following rules:
display: none;
height: 0;
transition: all 500ms;
The first Vue.set sets the height to auto so a accurate height can be taken with filterValuesEl.clientHeight
On the next tick the height is returned back to 0 then finally on the last tick it's set to its natural height.
However, it seems Vue.nextTick() isn't enough although I noticed adding an extremely small timeout seems to do the job. This works but feels quite messy. I was hoping someone might have a better solution?
I'm guessing that you're using filterStyles in a template, something like: :style="filterStyles". If that's the case, then
Vue.set(this.filterStyles, filterIndex, {
display: "block",
height: "auto",
});
won't actually set the height to auto immediately. Rather, it updates the component's data, and that update will be reflected in the DOM after a nextTick(). So, in effect, your code is off by one tick.
Stacking a series of nextTick calls one after the other is probably a bad practice, however. If, for example, the component is removed during the series, you'll be left with a dangling reference.
You could avoid all those ticks by just setting the style directly in order to measure its natural height. If the element has ref="filter" attribute, for example, then something like:
this.$refs.filter.style.height = "auto";
filterValuesElHeight = this.$refs.filter.clientHeight;
this.$refs.filter.style.height = "0";
You may have to work out conflicts between the template and the inline JavaScript (I can't tell because you don't show your template.), but that should get you started.
Related
I know that CSS transitions do not work with auto keyword, so if I want to apply transition: left 2s, right 2s; have to vary both right and left from (let's say) 0 to 100.
I have a script which might change an object's position either towards left or right, but I cannot set right: 100 if left is set to 0 (it has to be auto).
So I came out with this solution.
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0; //same position as auto but can be transitioned
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw"; //actual transition
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto"; //now I can move to either right or left again
//I don't need an animation on reset
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
This didn't work (the assign to 0 was completely ignored).
So I came out with this solution instead (same as above but using setTimeout(..., 0);
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0;
setTimeout(function() {
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw"; //this is executed async
}, 0);
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto";
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
This improved the result, but sometimes (it seems to be completely random, but more frequently on small screens), the transition still does not occur.
I supposed that this was because of the fact that sometimes the asynchronous function is executed too soon, so I tried to increase the delay to 10ms, but the problem still occurs sometimes (less frequently).
Now, I could increase the value furthermore, but:
I can never be sure that the error will not occur anyway sooner or later, maybe on a faster device (unless I set the timeout to a very large number)
I cannot set the timeout to a very large number, since I want it to be imperceptible
So, is there a minimum number that can assure a successful output?
If not, how can accomplish the same result?
As #Akxe suggested, the solution was reflowing the page. In other words, browsers flush multiple changes of the DOM all together. This speeds up the page by a bit but leads to some problems like the one I described (further information here)
As suggested here, the solution was a function like this one:
function reflow() { //this will be called between the two elements
void(document.documentElement.offsetWidth);
}
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0;
reflow(); //the line above is now flushed
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw";
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto";
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
The function actually worked for me even without using void, but I preferred keeping it since in the referenced question someone pointed out that it didn't work for him, and also for more clarity.
This could also be useful: it is an (unofficial) list of everything that causes page reflow.
When requesting the dimensions of an element currently in a CSS transition, jQuery will return the current value of the element's height at the given time of when the dimensions are requested.
While this is right and well, it often isn't what's needed. I have many cases in which I'd like to retrieve the final dimensions that the element will have after the transition, but while the transition is still in progress.
How can I reliably retrieve the dimensions of an element while its still in transition?
$(document).ready(function() {
$('#one').on('click', function() {
$(this).addClass('trans');
$('#output').text($(this).height());
var self = $(this);
setTimeout(function() {
$('#output').text(self.height());
}, 200);
});
});
#output {}
#one {
width: 200px;
height: 200px;
background: red;
transition: all 1s linear;
}
#one.trans {
height: 600px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div id="output">...</div>
<div id="one"></div>
See this codepen example
The thing about using setTimeout is that you're forced to use a somewhat arbitrary value. You can use requestAnimationFrame, which should force the code to execute after the transition
A lot of times, you need to wait until the second animation frame, so:
requestAnimationFrame(function() {
requestAnimationFrame(function() {
$('#output').text(self.height());
});
});
I'm sure there is a simple fix for this and I just am unable to piece it together... In the event that the link with the id of "light_off" is clicked then I want all the little changes to take place, that part is working, but they're happening too abruptly. How do I slow them down or fade into the changes so the transition looks smoother? Do I fadeIn? Add "slow" duration? Animate? And if so, how would I implement that properly? Gee, I hope that makes sense. Any help would be appreciated! Thank you!!
<script>
$(document).ready(function(){
$("#lights_off").click(function(){
$("#lights_off").fadeOut(1000);
$("#main").addClass(" lights_on");
$('#flavoredesign_logo').attr('src','img/logofinal.png');
$("#nav").css("color","#000000");
$("#nav").css("border-bottom"," #333 solid 1px");
});
});
</script>
You can also use $.animate()
However using animate you can't set color values, but only numeric values or use 'toggle's. w3 has an excellent guide for using it.
$(function() {
var on = true;
$('#lights').on('click', function() {
if ( on ) {
$( "#lights" ).animate({
width: 100,
height: 100
}, 1000 );
} else {
$( "#lights" ).animate({
width: 200,
height: 200
}, 1000 );
}
on = !on;
});
})
I created a fiddle with sizing of an element
you can use setTimeout(function(){ /you code/ }, 200) or use css animations / transitions
As pointed out in the comments, couldn't you use the CSS transition attribute to achieve smooth class changes? You can use this to give a time frame for transitioning between different property values. For example, if you wanted to give an animation time frame for transitioning between colours:
.light {
border-radius: 50%;
width: 100px;
height: 100px;
transition: background-color 1s; //Most properties can be manipulated through the transition attribute, e.g. width 1s
Then toggling between different classes with different values for background-colour:
.lights_off {
background-color: grey;
}
.lights_on {
background-color: yellow;
}
I've created a Fiddle that shows how this could be utilised to create a smooth transition.
How to read dimensions and move a div that is hidden before Vue transition starts? For example, a user clicks a button and I want to move a hidden div to appear under the button with a fade-in transition. I need to be able to both read the dimensions and move the top/left position of the hidden div before the transition starts.
Let's say I'm using v-show="active" on the div, where active is my reactive data property I want to set to true and be able to move the div before transition starts.
I've tried all these:
Move the div first, then on nextTick set active = true.
Use the javascript hook beforeEnter to try to move the div before transitions start.
Use the javascript hook enter (and 'done' callback) to try to move the div before transition starts.
Tried all the above with updating the DOM immediately with the new position before setting active = true. (In other words, not via data binding, but actually setting element style properties directly like this.$refs.content.style.top = '500px' to avoid any waiting on the virtual DOM.) However, ideally I would like to accomplish this without directly touching the DOM, but using nextTicks instead. Both approaches fail.
Tried with some success with a hacky transition: all .8ms ease-in, top 1ms, left 1ms.
Tried with success with moving the div first then setting active in a setTimeout. This is not the right solution though.
Update
Thanks to the accepted answer I was able to see that I can read dimensions on nextTick (by which time v-show has turned on display). However, it turns out I needed the transition to be all transition all .3s and that would cause the movement to be included. The DOM will gather up all the changes and apply them together, which means they get lumped into the transition that is later added by Vue. The solution ended up being that I needed to make the movements, then trigger the DOM to repaint first, then trigger the v-show to turn on. Here's an example method:
startTransition () {
this.$refs.content.offsetHeight // <-- Force DOM to repaint first.
this.isContentActive = true // <-- Turns on v-show.
},
Use v-bind:style to move your window and it all works as intended.
Update: To check the size of the popup itself, it has to be shown, so I'm using v-show instead of v-if. The first thing I do is make it visible; on the next tick, I can measure it and place it.
new Vue({
el: '.container',
data: {
top: 0,
left: 0,
width: 0,
show: false
},
methods: {
showFloater: function(evt) {
const t = evt.target;
this.show = true;
Vue.nextTick(() => {
const fEl = this.$el.querySelector('.floating');
this.top = t.offsetTop + 30;
this.left = t.offsetLeft;
this.width = fEl.offsetWidth;
setTimeout(() => this.show = false, 1000);
});
}
}
});
.container {
position: relative;
}
.floating {
border: thin solid black;
padding: 3em;
position: absolute;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div class="container">
<button #click="showFloater">Could go here</button>
<button #click="showFloater">Or here</button>
<transition name="fade">
<div v-show="show" class="floating" v-bind:style="{
top: top + 'px',
left: left + 'px'
}">
This window is {{width}}px wide.
</div>
</transition>
</div>
I have the following code which gets the left and top position of the element you are hovered over. It produces the right results in safari and IE but fails to get the position of the img that you hover over in Firefox - it returns 0, 0. Can anyone see why this might be??
I think it might be something to do with setting it as the variable as it seems to work if I place it straight in the function. I need to set it as a variable though so it can return to its original state.
$.fn.hoverAnimationTwo = function () {
return $(this).each(function () {
var originalLeftTwo = parseInt($(this).css("left"));
var originalTopTwo = parseInt($(this).css("top"));
return $(this).hover(function () {
$(this).animate({
width: "17px",
height: "17px",
left: originalLeftTwo - 5,
top: originalTopTwo - 5
}, 100);
},function () {
$(this).animate({
width: "7px",
height: "7px",
left: originalLeftTwo,
top: originalTopTwo
}, 100);
});
});
}
$(".myImg").hoverAnimationTwo();
The other thing which is very odd is that I can copy all of my code into jsfiddle and it seems to work.
EDIT:
OK... so it turns out this isn't a javascript issue as such. It is because elsewhere in the page I had given an element a class beginning with a numeric character which is entirely my bad and I should know better!
Browsers seem to have some inconsistencies when relying on CSS properties. Have you tried using .position() or .offset() (whichever is applicable to your needs)?
http://api.jquery.com/offset/ or http://api.jquery.com/position/