Angular中实现文字列表滚动的相关探索

222 阅读3分钟

萌新探路,各位大佬见笑。

首先贴上常见的一种方法:

v1.0

<div id="box">
  <ul id="ul">
    <li>第一条</li>
    <li>第二条</li>
    <li>第三条</li>
    <li>第四条</li>
  </ul>
  <ul>
    <li>第一条</li>
    <li>第二条</li>
    <li>第三条</li>
    <li>第四条</li>
  </ul>
</div>
* {
  margin: 0;
  padding: 0;
}
div {
  width: 100px;
  height: 63px; /* 必须 */
  overflow: hidden; /* 必须 */
  margin: 20% auto;
  border: 1px solid red;
  text-align: center;
  ul {
    list-style: none;
  }
  ul li {
    background-color: aqua;
    box-sizing: border-box;
    border: 1px solid #000;
  }
}
box: any;
ul: any;

ngAfterViewInit(): void {
  this.box = document.getElementById('box');
	this.ul = document.getElementById('ul');
	this.roll(50);
}

roll(t: any): void {
  this.box.scrollTop = 0; // 开始无滚动时设为0
  let timer = setInterval(this.rollStart, t); // 设置定时器,参数t用在这为间隔时间(单位毫秒),参数t越小,滚动速度越快
// 鼠标移入div时暂停滚动
  this.box.onmouseover = () => {
    clearInterval(timer);
  };
// 鼠标移出div后继续滚动
  this.box.onmouseout = () => {
    timer = setInterval(this.rollStart, t);
  };
}

// 开始滚动函数
rollStart(): void {
  // 正常滚动不断给scrollTop的值+1,当滚动高度大于列表内容高度时恢复为0
  if (this.box.scrollTop >= this.ul.scrollHeight) {
    this.box.scrollTop = 0;
  } else {
  	this.box.scrollTop++;
	}
}

到目前为止,上述写法看起来正常,功能也实现了。但是一般来说html里的ul li是不会像上方这么写的,而是使用ngFor来对li进行循环输出,所以我在这里对html代码进行进一步的优化。

 <div #scroller>
   <div #scrollItem>
     <div *ngFor="let item of list">
	{{item}}
     </div>
   </div>
   <div #scrollAdd></div>
</div>

然后我们在ts中加上以下代码:

@ViewChild('scroller', { static: false }) scroller: ElementRef;
@ViewChild('scrollItem', { static: false }) scrollItem: ElementRef;
@ViewChild('scrollAdd', { static: false }) scrollAdd: ElementRef;


// roll 方法中添加
const scrollItem = this.scrollItem.nativeElement;
const scrollAdd = this.scrollAdd.nativeElement;
scrollAdd.innerHTML = scrollItem.innerHTML;

乍一看没有什么问题了,但是运行后发现scrollIteminnerHTML值为空,也就是说在innerHTML这段代码执行的时候,实际上使用ngFor指令的li们还没有渲染出来。

就目前而言我只想到了一种方法,有大佬会其他方法的还望赐教。

我给出的解决方案是:使用自定义指令。

下面贴出我的完整解决代码:

v2.0

 <div #scroller>
   <div #scrollItem>
     <div
          *ngFor="let item of list; let last = last"
          [afterFinish]="last"
          appAfterFinish
          (ngAfterFinish)="rollBefore($event)"
          >
         {{item}}
     </div>
   </div>
   <div #scrollAdd></div>
</div>
@ViewChild('scroller', { static: false }) scroller: ElementRef;
@ViewChild('scrollItem', { static: false }) scrollItem: ElementRef;
@ViewChild('scrollAdd', { static: false }) scrollAdd: ElementRef;

...

/** 数据加载完毕后进行滚动 */
rollBefore(event: any): void {
  if (event) {
    this.roll(50);
  }
}

/** 滚动准备 */
roll(t: any): void {
  const father = this.scroller.nativeElement;
  const scrollItem = this.scrollItem.nativeElement;
  const scrollAdd = this.scrollAdd.nativeElement;
  scrollAdd.innerHTML = scrollItem.innerHTML;
  father.scrollTop = 0; // 开始无滚动时设为0
  let timer = setInterval(this.rollStart.bind(this), t); // 设置定时器,参数t用在这为间隔时间(单位毫秒),参数t越小,滚动速度越快
// 鼠标移入div时暂停滚动
father.onmouseover = () => {
  clearInterval(timer);
};
// 鼠标移出div后继续滚动
father.onmouseout = () => {
  timer = setInterval(this.rollStart.bind(this), t);
};
}

/** 开始滚动 */
rollStart(): void {
  const father = this.scroller.nativeElement;
  const scrollItem = this.scrollItem.nativeElement;
  // 正常滚动不断给scrollTop的值+1,当滚动高度大于列表内容高度时恢复为0
  if (father.scrollTop >= scrollItem.scrollHeight) {
    father.scrollTop = 0;
  } else {
    father.scrollTop++;
  }
}

自定义指令:

import {
  Directive,
  Input,
  EventEmitter,
  Output,
  AfterViewInit,
} from '@angular/core';

@Directive({
  selector: '[appAfterFinish]',
})
export class AfterFinishDirective implements AfterViewInit {
  @Input() afterFinish: boolean;
  @Output() ngAfterFinish: EventEmitter<boolean> = new EventEmitter();
  constructor() {}

  ngAfterViewInit(): void {
    if (this.afterFinish) {
      this.ngAfterFinish.emit(this.afterFinish);
    }
  }
}

我的切入点是ngFor指令向外暴露了一个last变量,这个变量的作用是获取ngFor渲染的最后一个元素。在ngFor进行渲染的过程中,如果还未渲染完成,last的值为false,渲染完成值为true,我们便可以利用last的特性,对其进行监听,等其值为true是,在进行innerHTML值的相关操作,在这里我使用了自定义指令来进行lasttrue之后的相关操作,由于操作时放在ngAfterViewInit里的,所以此时的ngFor已然渲染完毕。

以上。