Free AI web copilot to create summaries, insights and extended knowledge, download it at here
5884
Abstract
slate3d</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>); <span class="hljs-comment">/* Basically, no transform */</span>
}
<span class="hljs-comment">/* "leave-to": End result /</span>
<span class="hljs-selector-class">.groups-leave-to</span> {
<span class="hljs-comment">/ Move 100% of the parent div width to the left /</span>
<span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate3d</span>(-<span class="hljs-number">100%</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
}</pre></div><p id="c7de">And now making the second <code>div</code> to appear from the right:</p><div id="89f0"><pre><span class="hljs-comment">/ Remember we have "left: 100%;" for this div /</span>
<span class="hljs-selector-class">.categories-leave-from</span> {
<span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate3d</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>); <span class="hljs-comment">/ so no transform */</span>
}
<span class="hljs-comment">/* 100% - 100% = 0, that's what we want */</span>
<span class="hljs-selector-class">.categories-enter-to</span> {
<span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate3d</span>(-<span class="hljs-number">100%</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
}
<span class="hljs-comment">/* And let's add the same transform to the div itself, to materialize the end result /</span>
<span class="hljs-selector-class">.content-categories</span> {
<span class="hljs-attribute">left</span>: <span class="hljs-number">100%</span>;
<span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate3d</span>(-<span class="hljs-number">100%</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>); <span class="hljs-comment">/ <-- */</span>
}</pre></div><p id="e1b0">Finally, we’ll add the transition function to both active states:</p><div id="ba43"><pre><span class="hljs-selector-class">.groups-leave-active</span>,
<span class="hljs-selector-class">.categories-enter-active</span> {
<span class="hljs-attribute">transition</span>: transform <span class="hljs-number">0.3s</span> ease-in-out;
}</pre></div><p id="fedc">And here we go:</p><figure id="0bcb"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*RKjxHqJS2QDFKskVZ2wCOA.gif"><figcaption>GIF created with <a href="https://getkap.co/">Kap</a></figcaption></figure><p id="7dee">I’ll let you go over the code for handling the “Back” button. Spoil: it’s very similar.</p><h1 id="4849">2- Adaptive height</h1><p id="1a51">Notice all the empty white space below the second list?
That’s what I wanted to improve and make the white card height adapt to its content.</p><p id="36e4">To access DOM elements within our Vue.js code, we’ll add three <code>ref</code>: <code>parentRef</code>, <code>groupsRef</code> and <code>categoriesRef</code>.</p><div id="2178"><pre><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"parentRef"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"parent"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">Transition</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"groups"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"showGroups"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"groupsRef"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content-groups"</span>></span> ... <span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">Transition</span>></span>
<span class="hljs-tag"><<span class="hljs-name">Transition</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"categories"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"showCategories"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"categoriesRef"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content-categories"</span>></span> ... <span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">Transition</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></pre></div><p id="20e9">On a side-note, if your child components are custom components, I suggest to add an additional wrapping <code>div</code> around each child and set the <code>ref</code> at the wrapping <code>div</code> level. That is to avoid any unexpected issue since components using <code><script setup></code> are private by default (as explained <a href="https://vuejs.org/guide/essentials/template-refs.html#ref-on-component">here</a>).</p><p id="04c7">Our height transition will be triggered from a <code>watch</code>.
I’ll refactor a bit and introduce a <code>currentBlock</code> ref to make this watch more simple:</p><div id="eb9a"><pre><span class="hljs-keyword">const</span> currentBlock = ref<<span class="hljs-string">'groups'</span> | <span class="hljs-string">'categories'</span>>(<span class="hljs-string">'groups'</span>)
<span class="hljs-keyword">const</span> showGroups = <span class="hljs-title function_">computed</span>(<span class="hljs-function">() =></span> currentBlock.<span class="hljs-property">value</span> === <span class="hljs-string">'groups'</span>)
<span class="hljs-keyword">const</span> sho
Options
wCategories = <span class="hljs-title function_">computed</span>(<span class="hljs-function">() =></span> currentBlock.<span class="hljs-property">value</span> === <span class="hljs-string">'categories'</span>)</pre></div><div id="59f6"><pre><span class="hljs-title function_">watch</span>(currentBlock, <span class="hljs-function">() =></span> {
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {
<span class="hljs-keyword">const</span> currentHeight = parentRef.<span class="hljs-property">value</span>.<span class="hljs-property">offsetHeight</span>
<span class="hljs-keyword">const</span> childRef = currentBlock.<span class="hljs-property">value</span> === <span class="hljs-string">'categories'</span> ? categoriesRef : groupsRef
<span class="hljs-keyword">const</span> newHeight = childRef.<span class="hljs-property">value</span>.<span class="hljs-property">offsetHeight</span>
<span class="hljs-title function_">animateValue</span>({ <span class="hljs-attr">start</span>: currentHeight, <span class="hljs-attr">end</span>: newHeight, <span class="hljs-attr">duration</span>: <span class="hljs-number">350</span> })
})
})</pre></div><p id="c582">So all the interesting logic is hidden behind this <code>animateValue</code> function, let’s have a look.</p><div id="dfcb"><pre><span class="hljs-keyword">function</span> <span class="hljs-title function_">animateValue</span>(<span class="hljs-params">{ start, end, duration }</span>) {
<span class="hljs-keyword">let</span> startTimestamp
<span class="hljs-keyword">let</span> endTimestamp
<span class="hljs-comment">// Internal "step" function, called as many times as needed</span>
<span class="hljs-comment">// (Called ~40 times with Chrome, ~20 times with Safari...)</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">step</span>(<span class="hljs-params">timestamp</span>) {
<span class="hljs-keyword">if</span> (!startTimestamp) {
startTimestamp = timestamp
endTimestamp = startTimestamp + duration
}
<span class="hljs-keyword">const</span> elapsed = timestamp - startTimestamp
<span class="hljs-keyword">const</span> progress = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(elapsed / duration, <span class="hljs-number">1</span>)
<span class="hljs-keyword">const</span> valueNow = <span class="hljs-title function_">easeOutQuart</span>(progress, start, end - start, <span class="hljs-number">1</span>)
<span class="hljs-comment">// -> The important line where the actual height of the div changes</span>
parentRef.<span class="hljs-property">value</span>.<span class="hljs-property">style</span>.<span class="hljs-property">height</span> = <span class="hljs-string">`<span class="hljs-subst">${valueNow}</span>px`</span>
<span class="hljs-comment">// If still not the desired end value, keep animating</span>
<span class="hljs-keyword">if</span> (progress < <span class="hljs-number">1</span>) {
<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">requestAnimationFrame</span>(step)
}
}
<span class="hljs-comment">// Start animation</span>
<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">requestAnimationFrame</span>(step)
}
<span class="hljs-comment">// Some "hard-to-read" easing function</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">easeOutQuart</span>(<span class="hljs-params">t, b, c, d</span>) {
<span class="hljs-keyword">return</span> -c * ((t = t / d - <span class="hljs-number">1</span>) * t * t * t - <span class="hljs-number">1</span>) + b
}</pre></div><p id="002e">That leverages the window API <code>requestAnimationFrame</code> method. You can read more about this in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame">MDN web docs</a>.</p><p id="4559">And when using such API, a good practice is to check “caniuse”: <a href="https://caniuse.com/mdn-api_window_requestanimationframe">https://caniuse.com/mdn-api_window_requestanimationframe</a> ✅</p><p id="3472">I’ve used an <code>easeOutQuart</code> easing function, you can find many other examples here: <a href="https://spicyyoghurt.com/tools/easing-functions"><i>https://spicyyoghurt.com/tools/easing-functions</i></a></p><h1 id="0262">Side notes</h1><h2 id="2c2d">1- “prefers-reduced-motion”</h2><p id="879b">When working with transitions and animations, one should always pay attention to user’s preference regarding that matter. I’m talking about “<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduced-motion</a>”.
VueUse comes to the rescue: <a href="https://vueuse.org/core/usePreferredReducedMotion/">https://vueuse.org/core/usePreferredReducedMotion/</a></p><h2 id="3fdf">2- Bad performance on height animation</h2><p id="ebf8">⚠️ Note that we animate the CSS <code>height</code> property, which is usually not recommended for a matter of performance. Indeed, it will force the browser to repaint everything… It would be much better to use <code>transform: scaleY(...)</code> to expand the parent white background as needed!
More readings <a href="https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/">here</a> or <a href="https://www.freecodecamp.org/news/animating-height-the-right-way/">here</a>.</p><p id="e93a">Well, that’s about it 🙂</p><p id="0797">You can have a look at the whole code here: <a href="https://github.com/cedric25/slide-with-auto-height">https://github.com/cedric25/slide-with-auto-height</a></p><p id="6795">Or play with the final solution: <a href="https://slide-with-auto-height.vercel.app/">https://slide-with-auto-height.vercel.app/</a></p></article></body>