I followed this article for a vertical swipeable cards slider.
This question has two parts.
1. I cant understand on how to reverse the direction of slider when swiped down?
Here is the relevant codepen - https://codepen.io/bmarcelino/pen/vRYPXV
The relevant function to update the cards
function updateUi() {
requestAnimationFrame(function(){
elTrans = 0;
var elZindex = 5;
var elScale = 1;
var elOpac = 1;
var elTransTop = items;
var elTransInc = elementsMargin;
for(i = currentPosition; i < (currentPosition + items); i++){
if(listElNodesObj[i]){
listElNodesObj[i].classList.add('stackedcards-bottom', 'stackedcards--animatable', 'stackedcards-origin-bottom');
listElNodesObj[i].style.transform ='scale(' + elScale + ') translateX(0) translateY(' + (elTrans - elTransInc) + 'px) translateZ(0)';
listElNodesObj[i].style.webkitTransform ='scale(' + elScale + ') translateX(0) translateY(' + (elTrans - elTransInc) + 'px) translateZ(0)';
listElNodesObj[i].style.opacity = elOpac;
listElNodesObj[i].style.display = 'block';
listElNodesObj[i].style.zIndex = elZindex;
elScale = elScale - 0.04;
elOpac = elOpac - (1 / items);
elZindex--;
}
}
});
};
I am not particularly well versed in Javascript.
As of now the slider moves in only one direction when swiped - forward. I am looking to understand an implementation of adding the backward movement to the slider.
2. Regarding performance
Also, requestAnimationFrame
really helps out in providing a smooth experience while swiping. But is there a limit as to how many cards should be in DOM? I will be calling an API service to get the contents, since it will return media, so will simply setting opacity to 0 help out in any way reducing memory use?
The author argues that removing DOM would force the browser to repaint, which can impact performance substantially? But isn't that virtual list do? What is the performance to cost ratio in such scenarios?
The other option is to use the animate function to create something yourself by changing the height and margin-top of the element you want to slideUp. The other option is to reverse the actions of the slideDown and slideUp by using CSS. See the Pen amNxpN by Paul ( @paulund) on CodePen.
What if you want to reverse these actions by using slideUp to show the contents and use slideDown to hide the contents. You could use the jQuery UI slide function to change the direction of methods yourself but then you'll need to load the jQuery UI library with the jQuery library adding more weight to the application.
To reverse the scrolling direction of your touchpad using the Settings app, use these steps: Open Settings. Click on Devices. Click on Touchpad. Under the "Scroll and zoom" section, use the drop-down menu to select the Down motion scrolls down option.
Open your Settings app by pressing the Windows logo key + I shortcut on your keyboard. Once the Settings app is up and running, click on Devices. From the left menu, select Touchpad. Search for Scrolling Direction. In the Scrolling Direction menu, search for the option to reverse your scrolling direction. Enable reverse scrolling. This is it.
This is not going to be a complete answer but seeing as no one else has responded I'll give answering part 1 of your question a shot. Note that this is just an example to help you understand how to make the cards go backwards not a production ready solution.
To rewire the "Top" button to go backwards, you just need to make the following changes:
onSwipeTop()
change currentPosition = currentPosition + 1;
to currentPosition = currentPosition - 1;
onSwipeTop()
change transformUi(0, -1000, 0, topObj);
to transformUi(0, 0, 0, topObj);
. This hides the card going up animation.updateUi()
change i < (currentPosition + items)
to i <= (currentPosition + items)
. This fixes a bug where only two of the three cards are updated.Now try clicking Left or Right a couple times then click Top a couple times. Each time you click Top you should see a card come back.
Presumably you'll want to make a new button "Bottom" instead of rewiring "Top" and you'll probably also want to put limits on changes to currentPosition
so you can't go beyond the first/last card but this should at least get you started.
I hope this helps.
Following is a very brief explanation of how the script works, including a suggestion for getting it to reverse the direction and 'unswipe' cards. The reverse direction method was inspired by Rocky's excellent answer - he deserves full credit for the idea.
Once the document is loaded the script gets a list of all available cards. In your example the cards are hard coded elements in the DOM, and the list of cards, listElNodesObj
, is a list of those elements. This is important to remember: the cards are not an abstraction, they are fundamentally elements within the page. When you add media and data to your cards you will have to do so by attaching it to elements in the DOM (e.g. with data attributes).
The script gets the current card index called currentPosition
; to begin with this is the top card. It then displays the current card and the two cards behind it (currentPosition + 1
and currentPosition + 2
).
On an input the card is appropriately animated to fly off the left, right or top. The current card index is incremented by one, advancing by one into the stack. The new current card and the two cards behind it are displayed.
At the moment all actions - swipe left, right and top - all advance into the stack. To reverse the direction you need to listen for a new action (or repurpose a current one such as swipe top), and on that action the current card index is decremented by 1. Add a check for less than zero. currentPosition = Math.max( 0, currentPosition - 1 );
Rocky has answered with an excellent solution that implements this.
Now this implementation begins with all cards already present in the DOM. You appear to want to update your stack of cards from a backend API. To do this you need a way of popping the card off one end and adding a new one on the other end when swiping. As stated above your list of cards is tied very closely to the DOM so you will need to abstract it a little to achieve this. Create a list of elements that you fill from your API and populate your initial document with it (instead of the other way around). When you swipe, whether forwards or back, pop an element off the receding end and add a new element, populated from your API, to the advancing end. Interestingly both your list size and your current position will always stay the same.
If your current position is always one back from the top-most card, and the last card in the stack is one more than the last visible card, you will always have a card ready to animate into view.
A slight clarification of terms here: Altering something like opacity
causes a repaint, removing an element from the DOM, whether soft or hard removal, causes a reflow. Repaints are expensive because the browser must check the visibility of every element in the DOM; reflows are even more expensive because the layout must be recalculated. See What's the difference between reflow and repaint?
There are two ways you could limit the number of cards in the DOM. You could set display: none
which leaves it in memory and the DOM, but prevents the browser from considering it when reflowing or repainting. Or you could use parent.appendChild(child)
to add a card and parent.removeChild(child)
to remove the card, ensuring that no reference to that element exists in JavaScript once it's removed, and once the garbage collector runs the removed element will be physically removed from memory. Both will trigger a reflow. Elements with opacity: 0
will remain fully in the DOM for reflowing and repainting.
As to what gives the best performance: changing the opacity or removing from memory, that really depends on your implementation. I can give you a few relevant pointers though.
Memory constraints "Is there a limit as to how many cards should be in DOM?" Absolutely, but this depends on your data. If you have a very small total amount of cards you could indeed load them all at the beginning and hide swiped ones with opacity: 0
or display: none
. The animation fluidity might even be improved (see point on animation blocking computations below). The performance difference purely from higher memory use will almost certainly be unnoticeable as modern browsers have oodles of memory and will stop your script well before it needs pagefile or swap. If you really will have such a huge DOM in memory that performance would noticeably degrade, your content download time would be a much bigger issue.
However, much more to the point, you rightly ask if removing or adding elements is the whole point of a virtual list. Why keep an element in memory if you will never access it again, or why load an element that is so far down the list it may never be reached. Indeed you state that you will be accessing content from an API which strongly implies you'll be accessing the card content one at a time. Waiting till you have all the content from your API may take a very noticeable length of time; as you already seem to be aware it would provide a better experience to only access the cards that you need to fill a fixed size list. (If you intend to reverse the direction of the slider then you should keep at least one swiped card in memory so that when swiping back you aren't ruining the animation by pausing or sending an empty card as you wait to download the content)
Animation Blocking Computations Download times aside, the real performance advantage of display: none
and opacity: 0
for cards that are swiped or are too far back in the list is that their content is already present in the DOM. (And as stated above, opacity: 0
has one further advantage: it does not trigger a reflow). By comparison physically adding and removing elements from the DOM requires an extra computation, namely inserting or removing the card node and all its children in the DOM tree. If this is done synchronously then you will have an animation blocking computation, whereby the swipe animation cannot take place until the DOM tree update is completed.
However let's keep things in perspective. Firstly, adding and removing nodes from the DOM tree is typically extremely fast. innerHTML has been demonstrated to be marginally faster at adding to the DOM but much slower at removing, so pick your poison with care. But unless you're adding tens or hundreds of kilobytes of data to your cards, the operation will likely take less than a millisecond. Secondly, you state that you will be retrieving content for the list from an API, which implies an asynchronous connection. If you take care when constructing your asynchronous functions, your function to add content to the DOM will not necessarily interfere with the animation. (Not to imply that JavaScript is multi-threaded; it is single threaded but well constructed asynchronous code means the order in which functions are executed does not matter). If the cards being added or removed are done so when an animation is not queued, any performance hit will become both shorter and less perceivable.
Finally, every DOM manipulation is a new render update so you want to do as few manipulations as possible. You would therefore create the elements against each other in memory and only at the very end insert the highest element into the DOM. See Fastest DOM insertion . If you can contain all card data within that card then each swipe would require just two DOM manipulations. It depends on what media the cards contain, but it's conceivable that, at worst, adding and removing cards to the stack will take only tens of milliseconds in combined time.
GPU Most rendering engines have access to a GPU which can achieve far better efficiency than the CPU in drawing and compositing operations that involve large numbers of pixels. Render layers are not rendered with the GPU by default. The page GPU Accelerated Compositing in Chrome states,
While in theory every single RenderLayer could paint itself into a separate backing surface [i.e. a GPU accessible compositing layer], in practice this could be quite wasteful in terms of memory (VRAM especially).
To ensure an animation such as opacity change is rendered using the GPU, you need to access the animation in such a way that it is implicitly composited. The above page has a comprehensive list of how to do this, but essentially you'd use CSS animations to change opacity which prompts the browser to promote the element to a compositing layer. The current code you have presented uses JavaScript to update opacity each animation frame, thus the opacity change is not a candidate for implicit compositing. (You appear to be using 3D transforms for the movement animation which triggers implicit compositing, so that is likely already GPU optimised). Changing the code to use CSS animations is not a trivial task, but it would very likely improve performance, specifically the frame rate during animation. Of course bench-marking is required to verify this for your specific scenario, see https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/ for a discussion on why some GPU animations may run slower.
In summary, the performance increase due to dynamically adding and removing elements from the virtual list, while potentially marginally higher than having the entire list fully loaded, is likely to be only a extra few milliseconds per swipe. With an asynchronous implementation, the frame rate during the animation should not change. This should normally be an easy concession for the likely large savings in initial download time, but must be considered in conjunction with other details of your particular implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With