I have a carousel that takes a state of {pages: [...], currentPage: 0}. If I set currentPage = 1 I want the carousel to slide left. The same thing should happen if I increase the number again, and it should slide right I decrease it.
I can't work out how this should be done with immutable data. The animation shouldn't be represented in the state object (should it?), but storing a property on the React component removes its "pure" functional nature.
What's the best way to approach this problem?
Just show me an example
This doesn't use Immutable.js, but the current property is just a number, which is immutable in JS, so in some regards it's the same idea:
http://jsbin.com/ligejacolo/edit?js,output
IMO the best option is ReactCSSTransitionGroup
+1 for the unnecessarily snarky reference to ReactCSSTransitionGroup in the comments, if you can use that.
After that, let the browser manage state via CSS transitions
I would store the pages and currentPage as props, regardless of whether the values are Immutable.js instances (props are a stateful part of your UI, just like state, don't let the name fool you!). When they're stored as props, it provides a useful API to the users of your component.
If you're using CSS transitions then at any given moment you should be able to render the markup and classes based on this information.
For example, given the (over-simplified markup):
<div class=container>
<!-- set the class to something like "inner pane2" to go to the second pane -->
<div class=inner>
<span class=pane>...</span>
<span class=pane>...</span>
<span class=pane>...</span>
</div>
</div>
And some CSS like:
.container {
position: relative;
}
.inner {
position: absolute;
left: 0;
transition: left 0.3s ease-in-out;
}
.pane {
width: 100px;
}
.inner.pane2 {
left: -100px;
}
.inner.pane3 {
left: -200px;
}
The browser takes care of mid-transition changes to the properties.
Often times you'll need to know when the animation is done, which you can accomplish with a transitionend listener and isAnimating flag (which I would store in the state, not the props). Set isAnimating when currentPage changes and clear it on transitionend.
You'll also often need to know which direction you're transitioning, which you can accomplish with a previousPage prop.
Finally, if you're just using JavaScript
Keep a reference to the current transition in the state (possibly the jQuery selection used to start the animation, if that's what you're using). When the transition is done, remove the reference. If the currentPage changes mid-transition, call .stop() on the selection (or whatever API you're using).
Related
When having a mat-select inside mat-sidenav which is to be programmatically opened, the mat-select gets focused and (if content allows for it), the sidenav is scrolled to focused element.
How to prevent the focus and scroll? At the moment I'm using an ugly hack by adding additional mat-select on top of the mat-sidenav and setting height to 0 and overflow to hidden, but I'm wondering if there's a more proper and elegant solution to this.
A bare minimum demo of the issue: https://angular-bg7azm.stackblitz.io
Demo is using:
#angular/core # 5.2.0
#angular/material # 5.0.0-rc.3
I am using:
#angular/code # 4.3.0
#angular/material # 2.0.0-beta.12
I've been trying to find a good solution for essentially the same problem - not having any item initially focused on a dialog without using tabindex="-1". One solution I came up with is basically the same as your 'hack' idea but a little smarter. It's a very simple custom component that uses a CDK directive to grab initial focus, then removes itself on blur. The component's opacity and size is zero so it is invisible and takes up no space. Tab order is preserved other than that this component has initial focus. You can control which item has focus on the first TAB press by placing the custom component in your DOM before the element you want to be first in the effective tab order. Because the component is removed on blur via ngIf, it is no longer in the tab order once the user has interacted with something.
NOTE: The 'cdkFocusInitial' directive is from Material 5.x. In 2.0.0-beta.12 it is 'cdk-focus-initial'. Also - I've only tested it with 5.x.
Component code:
import { Component } from '#angular/core';
#Component({
selector: 'init-focus',
template: '<button cdkFocusInitial *ngIf="!blurred" (blur)="blurred = true"></button>',
styles: [':host() { opacity: 0; max-width: 0; min-width: 0; max-height: 0; min-height: 0; margin: 0; padding: 0; border-width: 0; }']
})
export class InitFocus {
public blurred = false;
constructor() { }
}
Partial HTML example (from your stackblitz):
<mat-list-item>
<init-focus></init-focus>
<mat-form-field>
<mat-select placeholder="Status">
<mat-option value="open">Openstaand</mat-option>
<mat-option value="assigned">Toegekend</mat-option>
<mat-option value="in_progress">In behandeling</mat-option>
<mat-option value="closed_resolved">Opgelost</mat-option>
<mat-option value="closed_not_resolved">Niet opgelost</mat-option>
<mat-option value="not_relevant">Niet relevant</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item>
I'm not sure how robust this is in varied use cases, and there might be ways to improve on this, but it seems to work well on dialogs and in a sidenav.
I have an animation and JS for alternating 2 divs and change their background images (from an array of a few dozens images), sort of interchangeable divs. Everything works just fine, however when the animation runs I can see that my CPU is at 100%. At first I thought it might be due to setInterval, however when I changed the code from alternating the images to just increase a number with each iteration and log it to console - I saw dramatic CPU overload decrease, of about 40-50%. So I understood it might be due to animation.
Here's my HTML code:
<div class="wallpaper wallpaper-1" id="wallpaper-1"></div>
<div class="wallpaper wallpaper-2" id="wallpaper-2"></div>
CSS:
.wallpaper {
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
-webkit-transform: translateZ(0);
-webkit-animation-timing-function: linear;
}
.animate {
-webkit-animation-name: fadeInOut;
-webkit-animation-duration: 6s;
}
#-webkit-keyframes fadeInOut {
0% {
opacity: 0;
-webkit-transform: scale(1);
}
16% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
-webkit-transform: scale(1.1);
}
}
And JS that makes it all tick:
Wallpapers.get().then(function(list) {
var wp1, wp2, divs = [], path, bgs = [], counterBgs = 0, bgsLength, currentId, doInterval;
wp1 = document.getElementById('wallpaper-1');
wp2 = document.getElementById('wallpaper-2');
divs = [ wp1, wp2 ];
path = 'assets/img/wallpapers/';
bgs = list.data;
bgsLength = bgs.length;
//Preload images
for(var i = 0; i < bgsLength-1; i++) {
var wp = new Image();
wp.src = path+list.data[i];
}
function manageBg() {
setInterval(function(){
doInterval();
}, 4000);
}
doInterval = function doInterval() {
currentId = counterBgs % bgsLength;
if (counterBgs % 2 === 0) {
wp1.style.backgroundImage = "url(" + path + bgs[currentId] + ")";
wp1.classList.add('animate');
wp1.style.zIndex = 1;
wp2.style.zIndex = 0;
setTimeout(function() {
wp1.classList.remove('animate');
}, 5950);
} else {
wp2.style.backgroundImage = "url(" + path + bgs[currentId] + ")";
wp2.classList.add('animate');
wp1.style.zIndex = 0;
wp2.style.zIndex = 1;
setTimeout(function() {
wp2.classList.remove('animate');
}, 5950);
}
counterBgs++;
};
doInterval();
manageBg();
});
Any ideas how to reduce the CPU overload?
The answer is will-change property of css.
will-change is a property that optimizes animations by letting the browser know which properties and elements are just about to be
manipulated, potentially increasing the performance of that particular
operation.
Article Source: will-change - css tricks
The will-change property will use Hardware Acceleration in order to reduce load of your CPU and allocating your CSS3 animation/transformation to the GPU.
The Old: The translateZ() or translate3d() Hack
For quite some time now, we’ve been using what has been known as the
translateZ() (or translate3d()) hack (sometimes also called the null
transform hack) to trick the browser into pushing our animations and
transforms into hardware acceleration. We’ve been doing that by adding
a simple 3D transformation to an element that will not be transforming
in three-dimensional space. For example, an element that’s animated in
two-dimensional space can be hardware-accelerated by adding this
simple rule to it:
transform: translate3d(0, 0, 0);
Hardware-accelerating an operation results in the creation of what is
known as a compositor layer that is uploaded to and composited by the
GPU. However, force-hacking layer creation may not always be the
solution to certain performance bottlenecks on a page. Layer creation
techniques can boost page speed, but they come with a cost: they take
up memory in system RAM and on the GPU (particularly limited on mobile
devices) and having lots of them can have a bad impact (especially on
mobile devices), so they must be used wisely and you need to make sure
that hardware-accelerating your operation will really help the
performance of your page, and that a performance bottleneck is not
being caused by another operation on your page.
In order to avoid layer-creation hacks, a new CSS property has been
introduced, that allows us to inform the browser ahead of time of what
kinds of changes we are likely to make to an element, thus allowing it
to optimize how it handles the element ahead of time, performing
potentially-expensive work preparing for an operation such as an
animation, for example, before the animation actually begins. This
property is the new will-change property.
The New: The Glorious will-change Property
The will-change property allows you to inform the browser ahead of
time of what kinds of changes you are likely to make to an element, so
that it can set up the appropriate optimizations before they’re
needed, therefore avoiding a non-trivial start-up cost which can have
a negative effect on the responsiveness of a page. The elements can be
changed and rendered faster, and the page will be able to update
snappily, resulting in a smoother experience.
For example, when using CSS 3D Transforms on an element, the element
and its contents might be promoted to a layer, as we mentioned
earlier, before they are composited in (drawn onto the screen) later.
However, setting up the element in a fresh layer is a relatively
expensive operation, which can delay the start of a transform
animation by a noticeable fraction of a second, causing that
noticeable “flicker”.
In order to avoid this delay, you can inform the browser about the
changes some time before they actually happen. That way, it will have
some time to prepare for these changes, so that when these changes
occur, the element’s layer will be ready and the transform animation
can be performed and then the element can be rendered and the page
updated in quickly.
Using will-change, hinting to the browser about an upcoming
transformation can be as simple as adding this rule to the element
that you’re expecting to be transformed:
will-change: transform;
You can also declare to the browser your intention to change an
element’s scroll position (the element’s position in the visible
scroll window and how much of it is visible within that window), its
contents, or one or more of its CSS property values by specifying the
name of the properties you’re expecting to change. If you expect or
plan to change multiple values/aspects of an element, you can provide
a list of comma-separated values. For example, if you’re expecting the
element to be animated and moved (its position changed), you can
declare that to the browser like so:
will-change: transform, opacity;
Specifying what exactly you want to change allows the browser to make
better decisions about the optimizations that it needs to make for
these particular changes. This is obviously a better way to achieve a
speed boost without resorting to hacks and forcing the browser into
layer creations that may or may not be necessary or useful.
Article Source - Everything You Need to Know About the CSS will-change Property
Your css will be
.wallpaper {
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
will-change: transform, opacity;
-webkit-animation-timing-function: linear;
}
.animate {
-webkit-animation-name: fadeInOut;
-webkit-animation-duration: 6s;
}
#-webkit-keyframes fadeInOut {
0% {
opacity: 0;
-webkit-transform: scale(1);
}
16% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
-webkit-transform: scale(1.1);
}
}
Other Useful Sources:
Everything You Need to Know About the CSS will-change Property
CSS-TRICKS Article
SNOOK.CA
W3C Editors Draft
Mozilla Developer Network
Browser Support
will-change: update browser support
I'm having a bit of trouble with vertically centering elements inside of grid column.
Typically I'd use table-cell for something like that, but I'm having problems due to the float nature of Susy. Everything I try seems to fall apart at some point.
For instance if I wanted to center these elements vertically in their respective column how would I do that, assuming I am using the default grid settings.
<div class="section">
<div class="col1">Some Text<br/>Some Text</div>
<div class="col2"><img src=""/></div>
<div class="col3">Some Text</div>
</div>
Much thanks for any help
If you want to use table-cell with Susy, you should. Susy was built to be taken apart and customized. You can use the built-in functions in any way you like. I'm no master of table-based layout, but it sounds like you are. As far as Susy is concerned, it would look something like this:
.section {
display: table;
}
.col1, .col2, .col3 {
display: table-cell;
vertical-align: middle;
}
.col1, .col3 {
width: span(1);
}
.col2 {
width: span(2);
}
The span function works the same way as the span mixin, but only returns a width value. Combine that with your table-cells, and you should be good to go.
We're talking about adding a table-cell output option that will do this for you. If you have ideas for how that should work, open up a github issue and we'll talk. I'd love to hear your thoughts.
The text-overflow:ellipsis; CSS property must be one of the few things that Microsoft has done right for the web.
All the other browsers now support it... except Firefox.
The Firefox developers have been arguing over it since 2005 but despite the obvious demand for it, they can't seem to actually bring themselves to implement it (even an experimental -moz- implementation would be sufficient).
A few years ago, someone worked out a way to hack Firefox 3 to make it support an ellipsis. The hack uses the -moz-binding feature to implement it using XUL. Quite a number of sites are now using this hack.
The bad news? Firefox 4 is removing the -moz-binding feature, which means this hack won't work any more.
So as soon as Firefox 4 is released (later this month, I hear), we're going to be back to the problem of having it not being able to support this feature.
So my question is: Is there any other way around this? (I'm trying to avoid falling back to a Javascript solution if at all possible)
[EDIT]
Lots of up-votes, so I'm obviously not the only one who wants to know, but I've got one answer so far which basically says 'use javascript'. I'm still hoping for a solution that will either not need JS at all, or at worst only use it as a fall-back where the CSS feature doesn't work. So I'm going to post a bounty on the question, on the off chance that someone, somewhere has found an answer.
[EDIT]
An update: Firefox has gone into rapid development mode, but despite FF5 now being released this feature still isn't supported. And now that the majority of users have upgraded from FF3.6, the hack is no longer a solution. The good news I'm told that it might be added to Firefox 6, which with the new release schedule should be out in only a few months. If that's the case, then I guess I can wait it out, but it's a shame they couldn't have sorted it sooner.
[FINAL EDIT]
I see that the ellipsis feature has finally been added to Firefox's "Aurora Channel" (ie development version). This means that it should now be released as part of Firefox 7, which is due out toward the end of 2011. What a relief.
Release notes available here: https://developer.mozilla.org/en-US/Firefox/Releases/7
Spudley, you could achieve the same thing by writing a small JavaScript using jQuery:
var limit = 50;
var ellipsis = "...";
if( $('#limitedWidthTextBox').val().length > limit) {
// -4 to include the ellipsis size and also since it is an index
var trimmedText = $('#limitedWidthTextBox').val().substring(0, limit - 4);
trimmedText += ellipsis;
$('#limitedWidthTextBox').val(trimmedText);
}
I understand that there should be some way that all browsers support this natively (without JavaScript) but, that's what we have at this point.
EDIT
Also, you could make it more neat by attaching a css class to all those fixed width field say fixWidth
and then do something like the following:
$(document).ready(function() {
$('.fixWidth').each(function() {
var limit = 50;
var ellipsis = "...";
var text = $(this).val();
if (text.length > limit) {
// -4 to include the ellipsis size and also since it is an index
var trimmedText = text.substring(0, limit - 4);
trimmedText += ellipsis;
$(this).val(trimmedText);
}
});
});//EOF
EDIT 09/30/2011
FF7 is out, this bug is resolved and it works!
EDIT 08/29/2011
This issue is marked as resolved and will be available in FF 7; currently set to release on 09/27/2011.
Mark your calendars and get ready to remove all those hacks you've put in place.
OLD
I have another answer: wait.
The FF dev team is in hot pursuit to resolve this issue.
They have tentative fix set for Firefox 6.
Firefox 6!! When will that come
out?!?
Easy there, imaginary, over-reactive person. Firefox is on the fast dev track. FF6 is set for release six weeks after Firefox 5. Firefox 5 is set for release June 21st, 2011.
So that puts the fix sometime in the beginning of August 2011...hopefully.
You can sign up for the mailing list following the bug from the link in the original poster's question.
Or you can click here; whichever is easiest.
I must say I'm a little disappointed that the only browser specific hack in my application is going to be to support FF4. The above javascript solution doesn't account for variable width fonts. Here is a more verbose script that accounts for this. The big problem with this solution is that if the element containing the text is hidden when the code is run then the width of the box isn't known. This was a deal breaker for me so I stopped working on/testing it... but I thought I'd post it here in case it is of use to someone. Be sure to test it well as my testing was less than exhaustive. I intended to add a browser check to only run the code for FF4 and let all the other browsers use their existing solution.
This should be available for fiddling here:
http://jsfiddle.net/kn9Qg/130/
HTML:
<div id="test">hello World</div>
CSS:
#test {
margin-top: 20px;
width: 68px;
overflow: hidden;
white-space: nowrap;
border: 1px solid green;
}
Javascript (uses jQuery)
function ellipsify($c){
// <div $c> content container (passed)
// <div $b> bounds
// <div $o> outer
// <span $i> inner
// </div>
// <span $d></span> dots
// </div>
// </div>
var $i = $('<span>' + $c.html() + '</span>');
var $d = $('<span>...</span>');
var $o = $('<div></div>');
var $b = $('<div></div>');
$b.css( {
'white-space' : "nowrap",
'display' : "block",
'overflow': "hidden"
}).attr('title', $c.html());
$o.css({
'overflow' : "hidden",
'width' : "100%",
'float' : "left"
});
$c.html('').append($b.append( $o.append($i)).append($d));
function getWidth($w){
return parseInt( $w.css('width').replace('px', '') );
}
if (getWidth($o) < getWidth($i))
{
while (getWidth($i) > (getWidth($b) - getWidth($d)) )
{
var content = $i.html();
$i.html(content.substr(0, content.length - 1));
}
$o.css('width', (getWidth($b) - getWidth($d)) + 'px');
}
else
{
var content = $i.html();
$c.empty().html(content);
}
}
It would be called like:
$(function(){
ellipsify($('#test'));
});
I have run into this gremlin over the past week as well.
Since the accepted solution does not account for variable width fonts and wwwhack's solution has a While Loop, I will throw in my $.02.
I was able to drastically reduce the processing time of my problem by using cross-multiplication. Basically, we have a formula that looks like this:
The variable x in this case is what we need to solve. When returned as an Integer, it will give the new length that the over-flowing text should be. I multiplied the MaxLength by 80% to give the ellipses enough room to show.
Here is a full html example:
<html>
<head>
<!-- CSS setting the width of the DIV elements for the table columns. Assume that these widths could change. -->
<style type="text/css">
.div1 { overflow: hidden; white-space: nowrap; width: 80px; }
.div2 { overflow: hidden; white-space: nowrap; width: 150px; }
.div3 { overflow: hidden; white-space: nowrap; width: 70px; }
</style>
<!-- Make a call to Google jQuery to run the javascript below.
NOTE: jQuery is NOT necessary for the ellipses javascript to work; including jQuery to make this example work -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
//Loop through each DIV element
$('div').each(function(index) {
var myDiv = this; //The original Div element which will have a nodeType of 1 (e.g. ELEMENT_NODE)
var divText = myDiv; //Variable used to obtain the text from the DIV element above
//Get the nodeType of 3 (e.g. TEXT_NODE) from the DIV element. For this example, it will always be the firstChild
divText = divText.firstChild;
//Create another variable to hold the display text
var sDisplayText = divText.nodeValue;
//Determine if the DIV element is longer than it's supposed to be.
if (myDiv.scrollWidth > myDiv.offsetWidth) {
//Percentage Factor is just a way of determining how much text should be removed to append the ellipses
//With variable width fonts, there's no magic number, but 80%, should give you enough room
var percentageFactor = .8;
//This is where the magic happens.
var sliceFactor = ((myDiv.offsetWidth * percentageFactor) * sDisplayText.length) / myDiv.scrollWidth;
sliceFactor = parseInt(sliceFactor); //Get the value as an Integer
sDisplayText = sDisplayText.slice(0, sliceFactor) + "..."; //Append the ellipses
divText.nodeValue = sDisplayText; //Set the nodeValue of the Display Text
}
});
});
</script>
</head>
<body>
<table border="0">
<tr>
<td><div class="div1">Short Value</div></td>
<td><div class="div2">The quick brown fox jumps over the lazy dog; lots and lots of times</div></td>
<td><div class="div3">Prince</div></td>
</tr>
<tr>
<td><div class="div1">Longer Value</div></td>
<td><div class="div2">For score and seven year ago</div></td>
<td><div class="div3">Brown, James</div></td>
</tr>
<tr>
<td><div class="div1">Even Long Td and Div Value</div></td>
<td><div class="div2">Once upon a time</div></td>
<td><div class="div3">Schwarzenegger, Arnold</div></td>
</tr>
</table>
</body>
</html>
I understand this is a JS only fix, but until Mozilla fixes the bug, I'm just not smart enough to come up with a CSS solution.
This example works best for me because the JS is called every time a grid loads in our application. The column-width for each grid vary and we have no control over what type of computer our Firefox users view our app (which, of course, we shouldn't have that control :) ).
This pure CSS solution is really close, except for the fact that it causes ellipsis to appear after every line.
CSS3 supports multiple background images, for example:
foo { background-image: url(/i/image1.jpg), url(/i/image2.jpg); }
I'd like to be able to add a secondary image to an element with a class though.
So for example, say you have a nav menu. And each item has a background image. When a nav item is selected you want to layer on another background image.
I do not see a way to 'add' a background image instead of redeclaring the whole background property. This is a pain because in order to do this with multi-backgrounds, you would have to write the base bg image over and over for each item if the items have unique images.
Ideally I'd be able to do something like this:
li { background: url(baseImage.jpg); }
li.selected { background: url(selectedIndicator.jpg); }
And have li.selected's end result appear the same if I did:
li.selected { background: url(baseImage.jpg), url(selectedIndicator.jpg); }
Update: I also tried the following with no luck (I believe backgrounds are not inherited..)
li { background: url(baseImage.jpg), none; }
li.selected { background: inherit, url(selectedIndicator.jpg); }
That is, in any case, not the way CSS inheritance works. inherit implies that an element should take on the attributes of it's parent element, not previous declarations affecting the same element.
What you want has been proposed as a way to make CSS more object-oriented, but the closest you will get is with a pre-processor like SASS.
For now you actually just have to re-state the first image along with the second.
I don't think this is possible, I think you'd have to redefine the whole rule every time.
For example, you could just add a "wrapper" around every item that has the initial background, with the actual item having a transparent background. Then add the background on the item itself when it's selected.
Additive CSS rules still aren't possible as far as I know.
You could try applying the second image to the ::after pseudo element:
li { background: url(baseImage.jpg); position: relative; }
li.selected::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: url(selectedIndicator.jpg);
}
I had the same need as you recently.
I finally thought about it and solved using css variables.
::root { --selectdropdown: url( '../elements/expand-dark.svg' ); }
select.gender.female { background-image: var(--selectdropdown), url( '../elements/female-dark.svg' ); }
When you resetting the attribute, just specify the variable again in the list!