欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Angular实现悬浮球组件

程序员文章站 2022-05-29 18:14:25
...

Angular实现悬浮球组件

在手机 App 上,我们经常会看到悬浮球的东东,用着可能很舒服,但是 web 网页上却很少见,今天我们就通过 Angular 来实现,当然使用其他框架也是可以的。

功能要求:

  • 支持设置直径
  • 支持点击触发信号
  • 支持设置鼠标按压时间

实现的过程中省略的部分天填坑过程。

此项目已经在github开源,欢迎大家 starfork。不胜感激。

开发环境

Angular CLI: 6.1.2
Node: 8.9.4
OS: linux mint 19 x64
IDE: vscode

确定悬浮球的样式

我大致找了几个比较漂亮一点的悬浮球作为参考:

1. 魅族手机悬浮球

Angular实现悬浮球组件

2. 小米手机的悬浮球

Angular实现悬浮球组件

3. 不知名

Angular实现悬浮球组件

看了这几个悬浮球,实在感觉一般般,接下来我们来试一试吧。

创建悬浮球组件

  1. 打开终端执行

    ng g c floatingBall

    此命令生成floating-ball组件,这里要感谢 Angular 脚手架的强大。

    floating-ball/
    
    ├── floating-ball.component.html
    ├── floating-ball.component.scss
    ├── floating-ball.component.spec.ts
    └── floating-ball.component.ts
    
    0 directories, 4 files

    修改css文件为scss,相应在ts文件中的Component装饰器中也要修改。

  2. html 文件增加如下

    <div id="floating-ball-container"
         (click)="clicked.emit();"
         [style.cursor]="currentCursorStyle"
         [style.width]="addUnit(outerCircleDiameter)"
         [style.height]="addUnit(outerCircleDiameter)">
      <div id="inner-circle"
           [style.width]="addUnit(innerCircleDiameter)" 
           [style.height]="addUnit(innerCircleDiameter)"
           [style.top]="addUnit(outerCircleDiameter / 2 - innerCircleDiameter / 2)"
           [style.left]="addUnit(outerCircleDiameter / 2 - innerCircleDiameter / 2)"></div>
    </div>

    html 主要是两个块级元素div,想了解块级和内联元素的区别点击这里。

    第一个div悬浮球容器,第二个div悬浮球的内圆。

  3. scss文件修改如下

    $inner-circle-bg: white;            // 内圆的背景色
    $container-bg-color: #F44336;       // 悬浮球容器的背景色
    $container-bg-start-color: #EF9A9A; // 悬浮球动画背景开始颜色
    
    // 悬浮球scss配置
    
    #floating-ball-container {
    
        position: fixed;
        z-index: 2000;   // 设置为最大原因是保持再所有元素的上层
        bottom: 20px;
        right: 20px;
        height: 60px;
        width: 60px;
        border: none;
        border-radius: 50%;
        opacity: 1;
    
        // 设置阴影效果
        box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
                    0 3px 14px 2px rgba(0, 0, 0, 0.12),
                    0 5px 5px -3px rgba(0, 0, 0, 0.2);
    
        // 动画效果, 一闪一闪的效果
        animation: twinkle 1.5s alternate infinite;
        -moz-animation:twinkle 1.5s alternate infinite; /* Firefox */
        -webkit-animation:twinkle 1.5s alternate infinite; /* Safari and Chrome */
        -o-animation:twinkle 1.5s alternate infinite; /* Opera */
    
        // 容器内的属性
        #inner-circle {
            position: relative;
            width: 30px;
            height: 30px;
            top: 15px;
            left: 15px;
            border-radius: 50%;
            background-color: $inner-circle-bg;
            opacity: 1;
        }
    }
    
    @keyframes twinkle{
        from{background: $container-bg-start-color;}
        to{background: $container-bg-color;}
    }
    
    @-moz-keyframes twinkle{ /* Firefox */
        from{background: $container-bg-start-color;}
        to{background: $container-bg-color;}
    }
    @-webkit-keyframes twinkle{ /* Safari and Chrome */
        from{background: $container-bg-start-color;}
        to{background: $container-bg-color;}
    }
    
    @-o-keyframes twinkle{ /* Opera */
        from{background: $container-bg-start-color;}
        to{background: $container-bg-color;}
    
    // 鼠标悬浮后的伪类样式设定
    
    #floating-ball-container:hover {
    
        animation-play-state: paused; // 所有动画停止
        // 阴影加深 24dp
        box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14),
        0  6px 30px 5px rgba(0, 0, 0, 0.12),
        0  8px 10px -5px rgba(0, 0, 0, 0.2);
    }
  4. ts文件修改如下

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

@Component({
  selector: 'app-floating-ball',
  templateUrl: './floating-ball.component.html',
  styleUrls: ['./floating-ball.component.scss']
})
export class FloatingBallComponent implements AfterViewInit {

  // 点击悬浮球信号
  @Output() public clicked = new EventEmitter();
  @Input() outerCircleDiameter = 60;    // 外圆直径
  @Input() innerCircleDiameter = 30;    // 内圆直径
  @Input() pressedTime = 500;           // 鼠标按压时间

  isPressed = false;   // 鼠标是否按下的标记

  posX = 0;            // 悬浮球的x轴位置
  posY = 0;            // 悬浮球的y轴位置

  lastMousePos = {    // 记录鼠标按下时的坐标
    x: 0,
    y: 0
  };

  mouseOffsetX = 0;    // 鼠标X偏移量
  mouseOffsetY = 0;    // 鼠标X偏移量
  elementOffsetX = 0;  // 悬浮球容器的X偏移量
  elementOffsetY = 0;  // 悬浮球容器的Y偏移量

  private timer: any;
  currentCursorStyle = 'default';
  private cursorStyle = { default: 'default', moved: 'move' };

  constructor() { }

  ngAfterViewInit() {
    const rootNode = document.getElementById('floating-ball-container');  // 获取容器元素
    const viewWidth = window.innerWidth;    // 获取窗口宽度
    const viewHeight = window.innerHeight;  // 获取窗口宽度

    rootNode.addEventListener('mousedown', (event) => {
      this.timer = setInterval(() => {
        this.isPressed = true; // 确认鼠标按下
        this.openMovedCursor(); // 打开可移动光标
      }, this.pressedTime);
      this.lastMousePos.x = event.clientX; // 记录鼠标当前的x坐标
      this.lastMousePos.y = event.clientY; // 记录鼠标当前的y坐标
      this.elementOffsetX = rootNode.offsetLeft; // 记录容器元素当时的左偏移量
      this.elementOffsetY = rootNode.offsetTop; // 记录容器元素的上偏移量
      event.preventDefault();                   // 取消其他事件
    }, false);

    // 此处必须挂载在document上,否则会发生鼠标移动过快停止
    document.addEventListener('mousemove', (event) => {
      if (this.isPressed) {// 如果是鼠标按下则继续执行
        this.mouseOffsetX = event.clientX - this.lastMousePos.x; // 记录在鼠标x轴移动的数据
        this.mouseOffsetY = event.clientY - this.lastMousePos.y; // 记录在鼠标y轴移动的数据
        this.posX = this.elementOffsetX + this.mouseOffsetX; // 容器在x轴的偏移量加上鼠标在x轴移动的距离
        this.posY = this.elementOffsetY + this.mouseOffsetY; // 容器在y轴的偏移量加上鼠标在y轴移动的距离
        rootNode.style.left = this.posX + 'px';
        rootNode.style.top = this.posY + 'px';
      }
    }, false);

    // 鼠标释放时候的函数
    document.addEventListener('mouseup', () => {
      this.isPressed = false;
      this.closeMovedCursor();
      clearInterval(this.timer);  // 释放定时器
    }, false);

    rootNode.addEventListener('touchmove', (event) => {
      event.preventDefault(); // 阻止其他事件
      if (event.targetTouches.length === 1) {
        const touch = event.targetTouches[0]; // 把元素放在手指所在的位置
        this.posX = touch.pageX; // 存储x坐标
        this.posY = touch.pageY; // 存储Y坐标
        rootNode.style.left = this.posX + 'px';
        rootNode.style.top = this.posY + 'px';
      }
    });
  }

  openMovedCursor(): void {
    if (this.currentCursorStyle === this.cursorStyle.moved) {
      return;
    }

    this.currentCursorStyle = this.cursorStyle.moved;
  }

  closeMovedCursor(): void {

    if (this.currentCursorStyle === this.cursorStyle.default) {
      return;
    }
    this.currentCursorStyle = this.cursorStyle.default;
  }

  addUnit(value: number): string {
    return value + 'px';
  }
}
  1. 在app中使用

    <app-floating-ball></app-floating-ball>
  2. 运行查看

    npm start

结果展示

Angular实现悬浮球组件