You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular Material 17 实现可交互垂直滑块的问题求助

Angular Material 17 实现可交互垂直滑块的问题求助

我刚帮几个开发者解决过类似的 Angular Material 17 垂直滑块问题,你的困扰我完全懂!先理清楚核心问题:Material 17 确实移除了官方垂直滑块的支持,连 legacy 模块也彻底砍掉了,单纯用 CSS rotate 会因为事件监听坐标不匹配导致交互失效,下面给你几个可行的解决方案,按优先级排序:


方案一:修复 CSS 旋转后的交互问题(最贴合你的现有代码)

单纯旋转滑块容器只会改变视觉呈现,但滑块内部的事件监听还是基于原始水平坐标的,所以我们需要手动转换鼠标/触摸事件的坐标。你可以写一个自定义指令来处理这件事:

  1. 先写 HTML 和基础样式:
<div class="vertical-slider-container" appVerticalSlider>
  <mat-slider class="vertical-slider" min="0" max="100" value="50"></mat-slider>
</div>
.vertical-slider-container {
  width: 40px;
  height: 200px;
  position: relative;
}

.vertical-slider {
  position: absolute;
  top: 0;
  left: 0;
  width: 200px;
  height: 40px;
  transform-origin: bottom left;
  transform: rotate(-90deg);
}
  1. 创建自定义指令 appVerticalSlider:
import { Directive, HostListener, ElementRef } from '@angular/core';

@Directive({
  selector: '[appVerticalSlider]'
})
export class VerticalSliderDirective {
  constructor(private el: ElementRef) {}

  @HostListener('mousedown', ['$event'])
  @HostListener('touchstart', ['$event'])
  onStart(event: MouseEvent | TouchEvent) {
    event.preventDefault();
    const sliderInput = this.el.nativeElement.querySelector('input');
    if (!sliderInput) return;

    const containerRect = this.el.nativeElement.getBoundingClientRect();
    const clientY = (event as MouseEvent).clientY || (event as TouchEvent).touches[0].clientY;
    const percent = (containerRect.bottom - clientY) / containerRect.height;
    
    sliderInput.value = String(Math.max(0, Math.min(100, percent * 100)));
    sliderInput.dispatchEvent(new Event('input'));
  }

  @HostListener('mousemove', ['$event'])
  @HostListener('touchmove', ['$event'])
  onDrag(event: MouseEvent | TouchEvent) {
    event.preventDefault();
    const sliderInput = this.el.nativeElement.querySelector('input');
    if (!sliderInput) return;

    const containerRect = this.el.nativeElement.getBoundingClientRect();
    const clientY = (event as MouseEvent).clientY || (event as TouchEvent).touches[0].clientY;
    const percent = Math.max(0, Math.min(1, (containerRect.bottom - clientY) / containerRect.height));
    
    sliderInput.value = String(percent * 100);
    sliderInput.dispatchEvent(new Event('input'));
  }

  @HostListener('mouseup')
  @HostListener('touchend')
  onEnd() {
    const sliderInput = this.el.nativeElement.querySelector('input');
    if (!sliderInput) return;
    sliderInput.dispatchEvent(new Event('change'));
  }
}

这个指令会把垂直方向的鼠标/触摸移动转换成滑块能识别的水平输入值,完美解决交互不顺畅的问题。


方案二:自定义 Material 风格垂直滑块(完全可控)

如果不想依赖原生 mat-slider,可以自己实现一个轻量的垂直滑块,样式完全匹配 Material Design,交互也更灵活:

<div class="custom-material-slider" (mousedown)="startDrag($event)" (touchstart)="startDrag($event)">
  <div class="slider-track">
    <div class="track-fill" [style.height.%]="value"></div>
  </div>
  <div class="slider-thumb" [style.bottom.%]="value"></div>
</div>
.custom-material-slider {
  width: 40px;
  height: 200px;
  position: relative;
  cursor: pointer;
}

.slider-track {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 100%;
  background-color: #e0e0e0;
  border-radius: 2px;
}

.track-fill {
  width: 100%;
  background-color: #6200ee;
  border-radius: 2px;
  position: absolute;
  bottom: 0;
}

.slider-thumb {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 24px;
  height: 24px;
  background-color: #6200ee;
  border-radius: 50%;
  cursor: grab;
  box-shadow: 0 2px 8px rgba(98, 0, 238, 0.3);
}

.slider-thumb:active {
  cursor: grabbing;
}
import { Component, HostListener, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-custom-vertical-slider',
  templateUrl: './custom-vertical-slider.component.html',
  styleUrls: ['./custom-vertical-slider.component.css']
})
export class CustomVerticalSliderComponent {
  @Input() value = 50;
  @Output() valueChange = new EventEmitter<number>();
  isDragging = false;

  @HostListener('document:mousemove', ['$event'])
  @HostListener('document:touchmove', ['$event'])
  onDrag(event: MouseEvent | TouchEvent) {
    if (!this.isDragging) return;
    event.preventDefault();

    const slider = event.target as HTMLElement;
    const rect = slider.getBoundingClientRect();
    const clientY = (event as MouseEvent).clientY || (event as TouchEvent).touches[0].clientY;
    
    this.value = Math.max(0, Math.min(100, (rect.bottom - clientY) / rect.height * 100));
    this.valueChange.emit(this.value);
  }

  @HostListener('document:mouseup')
  @HostListener('document:touchend')
  endDrag() {
    this.isDragging = false;
  }

  startDrag(event: MouseEvent | TouchEvent) {
    this.isDragging = true;
    this.onDrag(event);
  }
}

用法和原生组件一样,支持双向绑定:<app-custom-vertical-slider [(value)]="sliderValue"></app-custom-vertical-slider>


方案三:引入第三方组件库(最省心)

如果项目允许引入外部库,像 NG-ZORRO、PrimeNG 这类组件库都保留了垂直滑块的支持,而且样式和 Material 风格适配度很高,直接用现成组件就能解决问题。

备注:内容来源于stack exchange,提问作者Narek Bojikian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 11:23:10