Angular 结合 rxjs 实现拖拽

简介: 在上两篇文章中,我们学习了元素中必知重要属性和方法和 Angular 中自定义 Video 操作,没有度过的读者可先了解。那么,现在有这么一个需求,你会怎么实现呢?

image.png


一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情


在上两篇文章中,我们学习了元素中必知重要属性和方法 Angular 中自定义 Video 操作,没有度过的读者可先了解。


那么,现在有这么一个需求,你会怎么实现呢?


页面中 video 标签,当滚动高度超过其位置之后,将其设置为可在可视区域自由拖拽。


一个不错的 Idea,如果你使用 Angular@angular/cdk/drag-drop 可以轻松实现,但是我们这里不使用工具。

好吧,我们来分析下实现的思路:


  • 页面滚动高度大于视频所在的位置:那么就是视频的 bottom 值相对可视窗口的值要小于0,我们需要设定一个包裹 video 标签的 div 方便计算,其高度是原设定 video 的高度。即元素脱离原文档布局
  • video 元素可以拖拽,那么其定位需要被改变为 fixed
  • video 元素在可视区内自由拖动,那么需要对其 top, left 值进行限定


所以我们设定下面的 demo 布局:


<div id="anchor" #anchor>
  <div class="video" id="video" #video>
    <div class="masker"></div>
    <video width="100%" height="100%" controls poster="assets/poster.png">
      <source src="../assets/demo.mp4" type="video/mp4" />
      Your browser does not support.
    </video>
  </div>
</div>
复制代码


有下面这些预定的样式:


<!-- styles.scss -->
<!-- 这部分需要放在全局样式中 -->
html, body {
  height: 6000px;
  background-color: #fff;
}
复制代码


<!-- demo.component.scss -->
#anchor {
  height: 360px;
  width: 100%;
  background-color: #F0F0F0;
}
.video {
  width: 640px;
  height: 360px;
  margin: 0 auto;
  background-color: black;
  <!-- video fixed 布局的样式,默认布局中是没有的 -->
  &.video-fixed { 
    position: fixed;
    top: 10px;
    left: 10px;
    width: 320px;
    height: 150px;
    cursor: all-scroll;
    .masker {
         display: none;
      }
    &:hover {
      .masker {
        display: block;
        position: absolute;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.8);
        z-index: 2;
      }
    }
  }
}
复制代码


这里还引入了 rxjs 来操作。


元素脱离原文档布局



刚才已经分析了 video 元素脱离文档的临界调节了:


video 的外 div,即 #anchor 元素的相对视图的 bottom < 0。所以我们有:


@ViewChild('anchor', { static: false })
public anchor!: ElementRef;
@ViewChild('video', { static: false })
public video!: ElementRef;
public scroll!: any;
ngAfterViewInit(): void {
  this.scroll = fromEvent(document, 'scroll');
  this.scrollFn();
}
// 页面滚动
public scrollFn() {
  this.scroll
    .pipe(
      debounceTime(50), // 防抖
      map(() => this.anchor.nativeElement.getBoundindClientRect().bottom < 0)
    )
    .subscribe((flag: boolean) => {
      // 添加和移除样式
      if(flag) {
        this.video.nativeElement.classList.add('video-fixed');
      } else {
        this.video.nativeElement.classList.remove('video-fixed');
      }
    })
}
复制代码


image.png


先获取 anchor 元素对象,监听页面对象 document 滚动(我们这里加入了防抖函数优化),当 bottom < 0 的时候,将相关的样式 video-fixed 添加给 video


元素拖拽



接下来就是实现 video 元素的拖拽。这里我们要监听 video 元素的三个事件,分别是鼠标按下 mousedown,鼠标移动 mousemove 和鼠标抬起 mouseup


// demo.component.ts
public mouseDown!: any;
public mouseUp!: any;
public mouseMove!: any;
ngAfterViewInit(): void {
  this.mouseDown = fromEvent(this.video.nativeElement, 'mousedown'); // 目标元素按下,即 video
  this.mouseMove = fromEvent(document, 'mousemove'); // 元素在文档内移动
  this.mouseUp = fromEvent(document, 'mouseup'); // 鼠标抬起
  this.moveFn()
}
// 目标元素移动
public moveFn() {
  this.mouseDown
    .pipe(
      filter(() => this.video.nativeElement.classList.contains('video-fixed')),
      map(() => this.mouseMove.pipe(
        throttleTime(50), // 节流
        takeUntil(this.mouseUp)
      )),
      // concatAll 顺序接受上游抛出的各个数据流作为它的数据, 若前面的数据流不能同步的完结,它会暂存后续数据流,当前数据流完成后它才会订阅后一个暂存的数据流
      concatAll(),
      withLatestFrom(this.mouseDown, (move:any, down:any) => {
        return {
          x: this.validValue(move.clientX - down.offsetX, window.innerWidth - this.video.nativeElement.offsetWidth, 0),
          y: this.validValue(move.clientY - down.offsetY, window.innerHeight - this.video.nativeElement.offsetHeight, 0)
        }
      })
    )
    .subscribe((position: {
      x: number,
      y: number
    }) => {
      this.video.nativeElement.style.top = position.y + 'px';
      this.video.nativeElement.style.left = position.x + 'px';
    })
}
// 校验边界值
public validValue = (value:number, max:number, min: number) => {
  return Math.min(Math.max(value, min), max)
}
复制代码


我们监听目标元素(filter 函数)被鼠标按下,然后鼠标可以在 document 范围内移动(这里用节流函数优化了下),直到监听到鼠标抬起。在移动的过程中,计算目标元素的相对可视窗口左侧和顶部的距离,将值赋予到 lefttop


这里的计算 move.clientX - down.offsetX, window.innerWidth - this.video.nativeElement.offsetWidth,相关的概念也许你不是很清楚,不过没关系,上面👆的内容,理解思路即可。相关的知识点会在接下来的文章介绍。


最后,我们得到的效果如下👇


image.png


相关文章
|
4月前
|
JavaScript 前端开发 架构师
Angular进阶:理解RxJS在Angular应用中的高效运用
RxJS(Reactive Extensions for JavaScript)是JavaScript的一个响应式编程库,特别适用于处理异步数据流。
65 0
|
19天前
|
开发者 开发工具 UED
JSF应用的社交革命:一键解锁社交媒体超级功能,引爆用户参与度的奇迹!
【8月更文挑战第31天】本文探讨了在JavaServer Faces (JSF)应用中集成社交媒体功能的最佳实践,包括选择合适的API和SDK、示例代码及实现细节。通过集成社交媒体,应用能提供即时内容分享、互动交流和个性化体验,提升用户参与度。文章还强调了用户体验优化、安全性及隐私保护的重要性,并总结了社交媒体集成对企业竞争优势的意义。
25 0
|
12月前
|
JavaScript Shell
使用 RxJs 实现一个支持 infinite scroll 的 Angular Component
使用 RxJs 实现一个支持 infinite scroll 的 Angular Component
|
11月前
|
UED
关于 Angular 应用里 Rxjs filter 操作符内的双重感叹号的用法
关于 Angular 应用里 Rxjs filter 操作符内的双重感叹号的用法
|
12月前
|
前端开发 容器
使用 Rxjs 解决 Angular Component 之间的通信问题
使用 Rxjs 解决 Angular Component 之间的通信问题
|
12月前
|
前端开发 API
使用 RxJs Observable 来避免 Angular 应用中的 Promise 使用
使用 RxJs Observable 来避免 Angular 应用中的 Promise 使用
|
12月前
|
JavaScript
使用 RxJS timeout 操作符给 Angular SSR 服务器端渲染模式下的 HTTP 请求添加超时机制
使用 RxJS timeout 操作符给 Angular SSR 服务器端渲染模式下的 HTTP 请求添加超时机制
|
前端开发 JavaScript API
Angular与Rxjs学习
Angular与Rxjs学习
143 0
Angular与Rxjs学习
|
JavaScript
使用 RxJS timeout 操作符给 Angular SSR 服务器端渲染模式下的 HTTP 请求添加超时机制
Angular Universal 是一个开源项目,扩展了 @angular/platform-server 的功能。 该项目使 Angular 中的服务器端渲染成为可能。
149 0
|
API 索引
理解Angular RxJS映射数据操作
Map 数据是程序开发时的一种常见操作。当在代码中使用RxJS来生成数据流时,很可能最终需要一种方法来将数据映射成需要的任何格式。RxJS提供了常规的 map 函数,还有 mergeMap、switchMap和concatMap这样的函数,它们的处理方式略有不同。
150 0