Skip to content

Commit e793988

Browse files
authored
Merge pull request #370 from droans/next
Improve nav bar tab switch animation
2 parents 9ed6bdb + 9d0ed35 commit e793988

File tree

6 files changed

+70
-52
lines changed

6 files changed

+70
-52
lines changed

.github/release-drafter-dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ exclude-labels:
5757
- "no-changelog"
5858

5959
template: |
60-
# Music Assistant Queue Actions v$RESOLVED_VERSION
60+
# Music Assistant Player Card v$RESOLVED_VERSION
6161
6262
$CHANGES

.github/release-drafter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ exclude-labels:
5555
- "no-changelog"
5656

5757
template: |
58-
# Music Assistant Queue Actions v$RESOLVED_VERSION
58+
# Music Assistant Player Card v$RESOLVED_VERSION
5959
6060
$CHANGES

src/components/navigation-bar-expressive.ts

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Config } from "../config/config";
1818
import { MassCardController } from "../controller/controller";
1919
import { MediaBrowser } from "../sections/media-browser";
2020
import { Icons } from "../const/icons";
21+
import { WaAnimation } from "../const/elements";
2122

2223
class MassNavBar extends LitElement {
2324
private _controller!: MassCardController;
@@ -29,8 +30,10 @@ class MassNavBar extends LitElement {
2930
@query('#tab-media-browser') browserTab?: HTMLAnchorElement;
3031
@query('#tab-players') playersTab?: HTMLAnchorElement;
3132
@query('#tab-indicator') tabIndicator!: HTMLDivElement;
33+
@query('#animation') animationElement!: WaAnimation;
3234
@query('#navigation') navbar!: HTMLDivElement;
3335
private animationLength = 350;
36+
private animating = false;
3437

3538
@consume({ context: activeSectionContext, subscribe: true })
3639
@state()
@@ -89,21 +92,29 @@ class MassNavBar extends LitElement {
8992
}
9093
return elems[section];
9194
}
92-
private animateBounceLeft(to_element: HTMLAnchorElement): Keyframe {
95+
private animateBounceLeft(from_element: HTMLAnchorElement, to_element: HTMLAnchorElement): Keyframe {
96+
const from_left = from_element.offsetLeft;
97+
const from_width = from_element.offsetWidth;
98+
9399
const to_left = to_element.offsetLeft;
94100
const to_width = to_element.offsetWidth;
95101

96102
const bounce_move = to_width * 0.2;
97103

98104
const bounce_left = Math.max(0, to_left - bounce_move);
99105
const bounce_width = bounce_left > 0 ? to_width : to_width - (to_left + bounce_move)
106+
const translate_x = bounce_left - from_left;
107+
const scale_x = bounce_width / from_width;
108+
100109
return {
101-
left: `${bounce_left.toString()}px`,
102-
width: `${bounce_width.toString()}px`,
110+
transform: `translateX(${translate_x.toString()}px) scaleX(${scale_x.toString()})`,
103111
offset: 0.8,
104112
}
105113
}
106-
private animateBounceRight(to_element: HTMLAnchorElement): Keyframe {
114+
private animateBounceRight(from_element: HTMLAnchorElement, to_element: HTMLAnchorElement): Keyframe {
115+
const from_left = from_element.offsetLeft;
116+
const from_width = from_element.offsetWidth;
117+
107118
const to_left = to_element.offsetLeft;
108119
const to_width = to_element.offsetWidth;
109120

@@ -113,13 +124,15 @@ class MassNavBar extends LitElement {
113124

114125
const bounce_left = to_left + bounce_move;
115126
const bounce_width = to_elem_x_end >= navbarWidth ? navbarWidth - bounce_left : to_width;
127+
const translate_x = bounce_left - from_left;
128+
const scale_x = bounce_width / from_width;
116129
return {
117-
left: `${bounce_left.toString()}px`,
118-
width: `${bounce_width.toString()}px`,
130+
transform: `translateX(${translate_x.toString()}px) scaleX(${scale_x.toString()})`,
119131
offset: 0.8,
120132
}
121133
}
122134
private animateTabChange(from_section: Sections, to_section: Sections) {
135+
this.animating = true;
123136
if (from_section == to_section) {
124137
return;
125138
}
@@ -132,31 +145,30 @@ class MassNavBar extends LitElement {
132145
const from_left = from_elem.offsetLeft;
133146
const to_width = to_elem.offsetWidth;
134147
const to_left = to_elem.offsetLeft;
135-
const bounce = from_left - to_left > 0 ? this.animateBounceLeft(to_elem) : this.animateBounceRight(to_elem)
148+
const bounce = (from_left - to_left > 0)
149+
? this.animateBounceLeft(from_elem, to_elem)
150+
: this.animateBounceRight(from_elem, to_elem)
151+
152+
const translate_x = to_left - from_left;
153+
const scale_x = to_width / from_width;
154+
136155
const _keyframes = [
137-
{
138-
left: `${from_left.toString()}px`,
139-
width: `${from_width.toString()}px`
140-
}, // from
141156
bounce,
142157
{
143-
left: `${to_left.toString()}px`,
144-
width: `${to_width.toString()}px`
145-
}, // to
158+
transform: `translateX(${translate_x.toString()}px) scaleX(${scale_x.toString()})`,
159+
}
146160
]
147-
const keyframeEffect = new KeyframeEffect(
148-
this.tabIndicator,
149-
_keyframes,
150-
{
151-
// keyframe options
152-
duration: this.animationLength,
153-
// direction: "alternate",
154-
// easing: "ease-in-out",
155-
iterations: 1,
156-
},
157-
);
158-
const animation = new Animation(keyframeEffect, document.timeline);
159-
animation.play();
161+
const animation = this.animationElement;
162+
animation.keyframes = _keyframes;
163+
animation.addEventListener(
164+
'wa-finish',
165+
() => {
166+
this.animating = false;
167+
this.tabIndicator.style.left = `${to_left.toString()}px`;
168+
this.tabIndicator.style.width = `${to_width.toString()}px`;
169+
}
170+
)
171+
animation.play = true;
160172
}
161173

162174
protected renderMusicPlayerTab(): TemplateResult {
@@ -191,17 +203,6 @@ class MassNavBar extends LitElement {
191203
}
192204
return html``;
193205
}
194-
private renderIndicator(): TemplateResult {
195-
const elem = this.getSectionElement(this.active_section);
196-
const left = elem ? elem.offsetLeft : 0;
197-
const width = elem ? elem.offsetWidth : 113;
198-
return html`
199-
<div
200-
id="tab-indicator"
201-
style="left: ${left}px; width: ${width}px"
202-
></div>
203-
`
204-
}
205206
private renderTab(section: Sections, icon: string): TemplateResult {
206207
const active = this.active_section == section;
207208
return html`
@@ -231,7 +232,6 @@ class MassNavBar extends LitElement {
231232
/>
232233
${this.renderMusicPlayerTab()} ${this.renderQueueTab()}
233234
${this.renderMediaBrowserTab()} ${this.renderPlayersTab()}
234-
${this.renderIndicator()}
235235
</nav>
236236
</div>
237237
`;
@@ -242,6 +242,22 @@ class MassNavBar extends LitElement {
242242
}
243243
return _changedProperties.size > 0;
244244
}
245+
protected firstUpdated(): void {
246+
if (!this.tabIndicator) {
247+
const indicator = document.createElement('div');
248+
indicator.id = 'tab-indicator';
249+
const elem = this.getSectionElement(this.active_section);
250+
const left = elem?.offsetLeft ?? 0;
251+
indicator.style = `left: ${left.toString()}px;`
252+
253+
const animation = document.createElement('wa-animation') as WaAnimation;
254+
animation.id = 'animation'
255+
animation.duration = this.animationLength;
256+
animation.iterations = 1;
257+
animation.appendChild(indicator)
258+
this.navbar.appendChild(animation)
259+
}
260+
}
245261
static get styles(): CSSResultGroup {
246262
return styles;
247263
}

src/const/elements.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { LitElement } from "lit";
22

33
export interface WaAnimation extends LitElement {
44
play: boolean;
5+
keyframes?: Keyframe[],
6+
duration?: number,
7+
iterations?: number;
58
cancel?: () => void;
69
}
710

src/styles/head.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default css`
77
font-family: "Roboto Flex";
88
font-style: normal;
99
font-weight: 300;
10-
src: url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
10+
src: local("Roboto Flex") url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
1111
format("woff2");
1212
unicode-range:
1313
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
@@ -19,7 +19,7 @@ export default css`
1919
font-family: "Roboto Flex";
2020
font-style: normal;
2121
font-weight: 400;
22-
src: url(https://fonts.gstatic.com/s/robotoflex/v30/NaNnepOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl1cKq3tHXtXi8mzLjaAcbaknQ.woff2)
22+
src: local("Roboto Flex") url(https://fonts.gstatic.com/s/robotoflex/v30/NaNnepOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl1cKq3tHXtXi8mzLjaAcbaknQ.woff2)
2323
format("woff2");
2424
unicode-range:
2525
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
@@ -31,7 +31,7 @@ export default css`
3131
font-family: "Roboto Flex";
3232
font-style: normal;
3333
font-weight: 600;
34-
src: url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
34+
src: local("Roboto Flex") url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
3535
format("woff2");
3636
unicode-range:
3737
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
@@ -43,7 +43,7 @@ export default css`
4343
font-family: "Roboto Flex";
4444
font-style: normal;
4545
font-weight: 700;
46-
src: url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
46+
src: local("Roboto Flex") url(https://fonts.gstatic.com/s/robotoflex/v30/NaN4epOXO_NexZs0b5QrzlOHb8wCikXpYqmZsWI-__OGbt8jZktqc2V3Zs0KvDLdBP8SBZtOs2IifRuUZQMsPJtUsR4DEK6cULNeUx9XgTnH37Ha_FIAp4Fm0PP1hw45DntW2x0wZGzhPmr1YNMYKYn9_1IQXGwJAiUJVUMdN5YUW4O8HtSoXjC1z3QSabshNFVe3e0O5j3ZjrZCu23Qd4G0EBysQNK-QKavMl12JoUc.woff2)
4747
format("woff2");
4848
unicode-range:
4949
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,

src/styles/navigation-bar.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ export default css`
99
a.active-expressive {
1010
box-shadow: var(--md-sys-elevation-level2);
1111
}
12-
12+
:is(nav.tabbed>a,.tabs>a):is(:hover)::after {
13+
opacity: 0 !important;
14+
}
1315
.action-button-svg {
1416
--icon-primary-color: var(--tab-active-icon-color);
1517
height: var(--tab-icon-height);
@@ -41,6 +43,9 @@ export default css`
4143
box-shadow: var(--md-sys-elevation-level2);
4244
background-color: var(--md-sys-color-secondary-container);
4345
z-index: 1;
46+
will-change: transform;
47+
border-radius: 25% / 50%;
48+
width: 25%;
4449
}
4550
.tabbed {
4651
--tabbed-elevation: var(--md-sys-elevation-level1);
@@ -53,10 +58,4 @@ export default css`
5358
.tabbed-expressive {
5459
--tabbed-background-color: var(--md-sys-color-surface-container) !important;
5560
}
56-
57-
@keyframes move-indicator {
58-
to {
59-
left: var(--left-pos);
60-
}
61-
}
6261
`;

0 commit comments

Comments
 (0)