Skip to content

Commit

Permalink
feat(marquee): add fixed for marquee
Browse files Browse the repository at this point in the history
  • Loading branch information
Jerrylijieq committed Aug 7, 2024
1 parent 6a5db30 commit d728fdb
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 56 deletions.
7 changes: 7 additions & 0 deletions packages/banana-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @banana/banana-react

## 1.19.4

### Patch Changes

- Updated dependencies
- @banana-ui/banana@1.19.4

## 1.19.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/banana-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@banana-ui/react",
"version": "1.19.3",
"version": "1.19.4",
"description": "React components for Banana UI",
"keywords": [
"web components",
Expand Down
6 changes: 6 additions & 0 deletions packages/banana/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# banana-ui

## 1.19.4

### Patch Changes

- add fixed for marquee

## 1.19.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/banana/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@banana-ui/banana",
"version": "1.19.3",
"version": "1.19.4",
"description": "An UI library of web components can be used in any framework",
"keywords": [
"web components",
Expand Down
15 changes: 11 additions & 4 deletions packages/banana/src/marquee/index.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,21 @@ export default [
align-items: center;
}
.content {
.content-normal {
overflow: hidden;
display: inline-block;
flex: 0 0 auto;
white-space: nowrap;
animation: marquee var(--banana-marquee-duration) linear infinite;
min-width: 100%;
animation-play-state: var(--banana-marquee-fixed);
transform: translateX(var(--banana-marquee-width, 100%));
}
.content-fixed {
overflow: hidden;
display: inline-block;
flex: 0 0 auto;
white-space: nowrap;
transform: translateX(0);
}
@media (any-hover: hover) {
Expand All @@ -39,7 +46,7 @@ export default [
@keyframes marquee {
0% {
transform: translateX(0);
transform: translateX(var(--banana-marquee-width, 100%));
}
100% {
Expand Down
22 changes: 10 additions & 12 deletions packages/banana/src/marquee/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,42 +106,40 @@ describe('b-marquee', () => {
});

describe('custom fixed', () => {
it('should set the fixed when provided a boolean', async () => {
const element = await fixture<BMarquee>(html`<b-marquee fixed></b-marquee>`);
const fixed = window.getComputedStyle(element).getPropertyValue('--banana-marquee-fixed');
expect(fixed).to.equal('paused');
});

it('pauses animation when content width is less than marquee width', async () => {
const element = await fixture<BMarquee>(html`<b-marquee fixed content="short content"></b-marquee>`);

// Simulate content width being less than marquee width
element._mainContent!.getBoundingClientRect = () => ({ width: 50 } as DOMRect);
element._marquee!.getBoundingClientRect = () => ({ width: 100 } as DOMRect);
element._content!.getBoundingClientRect = () => ({ width: 50 } as DOMRect);

// 强制调用私有方法
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(element as any)?.firstUpdated();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(element as any)?._calculateWidth();

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect((element as any)._animationPlayState).to.equal('paused');
expect(element.style.getPropertyValue('--banana-marquee-fixed')).to.equal('paused');
expect((element as any)._isNormal).to.equal(false);
expect(element.style.getPropertyValue('--banana-marquee-width')).to.equal('100px');
});

it('resumes animation when content width is greater than marquee width', async () => {
const element = await fixture<BMarquee>(html`<b-marquee fixed content="a very very long content"></b-marquee>`);

// Simulate content width being greater than marquee width
element._mainContent!.getBoundingClientRect = () => ({ width: 200 } as DOMRect);
element._marquee!.getBoundingClientRect = () => ({ width: 100 } as DOMRect);
element._content!.getBoundingClientRect = () => ({ width: 200 } as DOMRect);

// 强制调用私有方法
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(element as any)?.firstUpdated();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(element as any)?._calculateWidth();

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect((element as any)._animationPlayState).to.equal('running');
expect(element.style.getPropertyValue('--banana-marquee-fixed')).to.equal('running');
expect((element as any)._isNormal).to.equal(true);
expect(element.style.getPropertyValue('--banana-marquee-width')).to.equal('100px');
});
});
});
70 changes: 32 additions & 38 deletions packages/banana/src/marquee/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CSSResultGroup, html, LitElement, PropertyValueMap } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import styles from './index.styles';

Expand All @@ -16,7 +16,6 @@ export default class BMarquee extends LitElement {
super.disconnectedCallback();
// 停止观察元素的尺寸变化
this._marquee && this.resizeObserver?.unobserve(this._marquee);
this._mainContent && this.resizeObserver?.unobserve(this._mainContent);
}

@property()
Expand All @@ -32,47 +31,44 @@ export default class BMarquee extends LitElement {
@property({ type: Boolean, attribute: 'pause-when-hover' })
pauseWhenHover = false;

@property({ type: Boolean })
@property({ type: Boolean, reflect: true })
fixed = false;

private _animationPlayState: 'running' | 'paused' = 'running';
// 判断是否是 normal还是fixed (fixed为true时,才生效)
@state()
_isNormal = true;

@query('.marquee')
_marquee: HTMLDivElement | undefined;

@query('#main-content')
_mainContent: HTMLDivElement | undefined;
@query('.content')
_content: HTMLDivElement | undefined;

firstUpdated() {
this._setBananaMarqueeWidth();

// 观察元素的尺寸变化
if (this._marquee && this._mainContent && this.fixed) {
if (this._marquee) {
this.resizeObserver = new ResizeObserver(() => this._calculateWidth());

this.resizeObserver?.observe(this._marquee);
this.resizeObserver?.observe(this._mainContent);
}
}

private _calculateWidth() {
if (this._marquee && this._mainContent && this.fixed) {
// marquee的宽度变化了 重新设置marquee的宽度
this._setBananaMarqueeWidth();

if (this._marquee && this._content && this.fixed) {
const marqueeWidth = this._marquee.getBoundingClientRect().width;
const contentWidth = this._mainContent.getBoundingClientRect().width;

if (contentWidth > marqueeWidth) {
this._animationPlayState = 'running';
this._setStyleFixed();
} else {
if (this._animationPlayState === 'running') {
// 暂停的时候等上一次的动画是否结束 结束后才暂停 这样比较友好
this._animationPlayState = 'paused';
this._mainContent.addEventListener('animationiteration', this._setStyleFixed.bind(this), { once: true });
}
}
const contentWidth = this._content.getBoundingClientRect().width;

this._isNormal = contentWidth > marqueeWidth;
}
}

private _setStyleFixed() {
this.style.setProperty('--banana-marquee-fixed', this._animationPlayState);
private _setBananaMarqueeWidth() {
this.style.setProperty('--banana-marquee-width', `${this._marquee!.getBoundingClientRect().width}px`);
}

protected willUpdate(_changedProperties: PropertyValueMap<this>): void {
Expand All @@ -85,25 +81,23 @@ export default class BMarquee extends LitElement {
const duration = this.duration;
this.style.setProperty('--banana-marquee-duration', `${duration}s`);
}

if (_changedProperties.has('fixed')) {
const fixed = this.fixed;
this._animationPlayState = fixed ? 'paused' : 'running';
this._setStyleFixed();
}
}

render() {
const marqueeClass = classMap({
marquee: true,
'marquee--pause-when-hover': this.pauseWhenHover,
});

const contentClass = classMap({
content: true,
'content-normal': this._isNormal,
'content-fixed': !this._isNormal,
});

return html`
<div
part="base"
class=${classMap({
marquee: true,
'marquee--pause-when-hover': this.pauseWhenHover,
})}
>
<div id="main-content" part="content" class="content">${this.content}</div>
<div part="content" class="content">${this.content}</div>
<div part="base" class=${marqueeClass}>
<div part="content" class=${contentClass}>${this.content}</div>
</div>
`;
}
Expand Down
2 changes: 2 additions & 0 deletions public/Marquee/fixedUsage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<b-marquee fixed
content="Lorem ipsum dolor sit amet, consectetur adipiscing elit."></b-marquee>

0 comments on commit d728fdb

Please sign in to comment.