@ray-js/ipc-player-integration 0.0.50-beta.1 → 0.0.50-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/lib/ctx/ctx.composition.js +5 -5
- package/lib/hooks/useBattery/index.js +7 -1
- package/lib/iconfont/iconfont.css +127 -7
- package/lib/iconfont/iconfont.js +10 -10
- package/lib/iconfont/iconfont.json +234 -24
- package/lib/iconfont/iconfont.ttf +0 -0
- package/lib/iconfont/iconfont.woff +0 -0
- package/lib/iconfont/iconfont.woff2 +0 -0
- package/lib/interface.d.ts +1 -1
- package/lib/interface.js +1 -1
- package/lib/ports.output.d.ts +13 -0
- package/lib/ports.output.js +13 -0
- package/lib/ui/bottomLeftContent.js +141 -27
- package/lib/ui/bottomRightContent.d.ts +6 -1
- package/lib/ui/bottomRightContent.js +43 -5
- package/lib/ui/ui.js +101 -21
- package/lib/ui/ui.less +17 -0
- package/lib/widgets/battery/battery.js +11 -3
- package/lib/widgets/battery/battery.less +11 -0
- package/lib/widgets/battery/batteryFull.js +11 -3
- package/lib/widgets/voiceIntercom/voiceIntercom.js +6 -1
- package/lib/widgets/zoomTurntable/constants.d.ts +187 -32
- package/lib/widgets/zoomTurntable/constants.js +306 -45
- package/lib/widgets/zoomTurntable/measureVisible.d.ts +9 -0
- package/lib/widgets/zoomTurntable/measureVisible.js +10 -0
- package/lib/widgets/zoomTurntable/turntable/index.js +9 -1
- package/lib/widgets/zoomTurntable/turntable/rjs/index.js +27 -4
- package/lib/widgets/zoomTurntable/turntable/rjs/index.rjs +265 -36
- package/lib/widgets/zoomTurntable/turntable/rjs/index.tyml +1 -0
- package/lib/widgets/zoomTurntable/turntable/type.d.ts +41 -2
- package/lib/widgets/zoomTurntable/useLensZoom.d.ts +3 -0
- package/lib/widgets/zoomTurntable/useLensZoom.js +96 -4
- package/lib/widgets/zoomTurntable/zoomTurntableDock.js +143 -13
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.js +116 -23
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.less +11 -1
- package/lib/widgets/zoomTurntable/zoomTurntableTip.js +34 -22
- package/lib/widgets/zoomTurntable/zoomTurntableTip.less +23 -7
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 手势 / 初始化链路日志开关。定位手势问题期间打开,稳定后置 false。
|
|
2
|
-
const DEBUG_GESTURE =
|
|
2
|
+
const DEBUG_GESTURE = false;
|
|
3
3
|
function dlog() {
|
|
4
4
|
if (!DEBUG_GESTURE) return;
|
|
5
5
|
var args = Array.prototype.slice.call(arguments);
|
|
@@ -16,8 +16,12 @@ const END_ANGLE = 360;
|
|
|
16
16
|
const ARC_SPAN = END_ANGLE - START_ANGLE;
|
|
17
17
|
// 指针固定在弧线中点(顶部)
|
|
18
18
|
const POINTER_ANGLE = START_ANGLE + ARC_SPAN / 2;
|
|
19
|
-
//
|
|
20
|
-
|
|
19
|
+
// 线宽 / 虚线 / 圆点 / 标签偏移这类几何元素的缩放系数。
|
|
20
|
+
// 数值沿用旧的轨道比例 5/6,保证轨道改成等距内收后这些细节尺寸不变。
|
|
21
|
+
const GEO_SCALE = 5 / 6;
|
|
22
|
+
// 轨道相对遮罩的默认内收距离(scale=1 时的 px),可由 styleOptions.trackEdgeInset 覆盖。
|
|
23
|
+
// 两条弧线按「同一个绝对间距」内收,而不是按同一个比例 —— 原因见 _computeGeometry。
|
|
24
|
+
const DEFAULT_TRACK_EDGE_INSET = 30;
|
|
21
25
|
// 遮罩本身也保持同一椭圆比例
|
|
22
26
|
const MASK_RX_RATIO = 1;
|
|
23
27
|
const MASK_RY_RATIO = 1;
|
|
@@ -29,9 +33,32 @@ const MINOR_DIVS = 60;
|
|
|
29
33
|
const MAJOR_SKIP = 0.004;
|
|
30
34
|
const LABEL_WINDOW = 0.28;
|
|
31
35
|
const TOP_SAFE_AREA = 50;
|
|
36
|
+
// 点击(非拖动)判定:手指位移与按压时长都在阈值内才算 tap
|
|
37
|
+
const TAP_MOVE_TOLERANCE = 10;
|
|
38
|
+
const TAP_MAX_MS = 350;
|
|
39
|
+
// tap 命中弧带的归一化半径范围(1 = 刻度轨道所在的椭圆)。
|
|
40
|
+
// 太靠圆心或太靠外都不算点刻度,直接忽略,避免误触把倍率甩飞。
|
|
41
|
+
const TAP_HIT_MIN_R = 0.55;
|
|
42
|
+
const TAP_HIT_MAX_R = 1.4;
|
|
43
|
+
// 点击后滚到目标刻度的动画时长(ms)
|
|
44
|
+
const TAP_ANIM_MS = 220;
|
|
32
45
|
// 拖动方向:-1 表示「拖动的是转盘本身」——向左拖,盘面左移,右侧的大刻度转到指针处,倍率变大,
|
|
33
46
|
// 与实体相机的调焦环手感一致;+1 则是「拖动指针」的反向手感。
|
|
34
47
|
const DRAG_DIRECTION = -1;
|
|
48
|
+
/**
|
|
49
|
+
* 横屏的位移轴向修正。
|
|
50
|
+
*
|
|
51
|
+
* 横屏画布整体 `translate(0, baseWidth) + rotate(-90°)`,绘制 x 轴因此映射到屏幕的**向上**方向 ——
|
|
52
|
+
* progress 越大(倍率越大)的刻度落在屏幕越上方。而手势取的是 clientY(向下为正),
|
|
53
|
+
* 与绘制 x 轴正好反向,不补这一下 DRAG_DIRECTION 在横屏就被翻转成「拖动指针」的手感:
|
|
54
|
+
* 向上拖倍率变大,跟实体相机的调焦环相反。
|
|
55
|
+
*
|
|
56
|
+
* 补上之后横竖屏统一为「拖动盘面」:竖屏向左拖 = 把右侧的大刻度拖到指针处 = 变大;
|
|
57
|
+
* 横屏向下拖 = 把上方的大刻度拖到指针处 = 变大。
|
|
58
|
+
*/
|
|
59
|
+
const RIGHT_AXIS_SIGN = -1;
|
|
60
|
+
// 遮罩平底边向外多铺的像素,抵消亚像素舍入造成的 1px 缝
|
|
61
|
+
const EDGE_OVERFILL = 1.5;
|
|
35
62
|
|
|
36
63
|
class ZoomTurntableRenderer {
|
|
37
64
|
constructor(options) {
|
|
@@ -56,7 +83,11 @@ class ZoomTurntableRenderer {
|
|
|
56
83
|
: this._style('cropRatio', 0.2);
|
|
57
84
|
this.callMethod = options.callMethod;
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
// 起始值必须落在 [min, max] 内:外部传进来的初值可能是尚未就绪的占位值(0 / NaN),
|
|
87
|
+
// 非受控模式下没有后续回写来纠正,直接夹住
|
|
88
|
+
var initial = Number(options.defaultValue);
|
|
89
|
+
if (!(initial > 0)) initial = this.min;
|
|
90
|
+
this.value = Math.max(this.min, Math.min(this.max, initial));
|
|
60
91
|
// progress(0~1) 是拖动时的唯一真相,保持浮点,避免 step 量化造成的抖动
|
|
61
92
|
this.progress = this.getProgress(this.value);
|
|
62
93
|
this.dragging = false;
|
|
@@ -96,13 +127,31 @@ class ZoomTurntableRenderer {
|
|
|
96
127
|
this.scale = contentScale;
|
|
97
128
|
this.scaleX = contentScale;
|
|
98
129
|
this.scaleY = contentScale;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
130
|
+
// 横半径同样允许被 styleOptions.ellipseRx 覆盖。竖屏必须跟着窗口宽度缩放:
|
|
131
|
+
// 弧顶到屏幕两侧的落差取决于「屏幕半宽 / 横半径」这个比值,横半径写死 258 的话,
|
|
132
|
+
// 屏幕越宽落差越大(430 宽机型上两侧只剩 15px),各机型弧形就不一致了。
|
|
133
|
+
this.maskRx = this._style('ellipseRx', BASE_RX) * contentScale * MASK_RX_RATIO;
|
|
134
|
+
// 纵半径允许被 styleOptions.ellipseRy 覆盖:竖屏要求整条背景带只有 ~70px 高,
|
|
135
|
+
// 按设计稿比例(516:351)的椭圆装不下。这里只压扁纵向、不动横向 ——
|
|
136
|
+
// 整体缩小会让背景撑不满播放器宽度,两侧会露出直角缺口。
|
|
137
|
+
this.maskRy = this._style('ellipseRy', BASE_RY) * contentScale * MASK_RY_RATIO;
|
|
138
|
+
// 轨道(含刻度点、指针)与标记文字向椭圆内侧「等距」收一圈:rx、ry 各减同一个绝对值,
|
|
139
|
+
// 而不是各乘同一个比例。按比例收的话弧顶间距 = maskRy × k、两侧间距 = maskRx × k,
|
|
140
|
+
// 椭圆越扁两者差得越多(旧的 5/6 + inset 方案在竖屏是弧顶 39px / 两侧 65px),
|
|
141
|
+
// 外圈背景看起来就比内部描边更扁;等距内收才是一对弧度一致的平行弧。
|
|
102
142
|
// 遮罩半径不变,所以 canvas 尺寸与截面都不受影响。
|
|
103
|
-
this.trackEdgeInset = this._style('trackEdgeInset',
|
|
104
|
-
this.
|
|
105
|
-
|
|
143
|
+
this.trackEdgeInset = this._style('trackEdgeInset', DEFAULT_TRACK_EDGE_INSET) * contentScale;
|
|
144
|
+
this.ry = this.maskRy - this.trackEdgeInset;
|
|
145
|
+
/**
|
|
146
|
+
* 轨道横半径可以单独指定(styleOptions.trackRx),不指定就跟纵向一样等距内收。
|
|
147
|
+
*
|
|
148
|
+
* 竖屏要的是「虚线弧两端扎出播放器底边」:轨道椭圆比播放器窄一些,弧才会在到达
|
|
149
|
+
* 屏幕左右边缘之前先降到底边以下、被 canvas 底边切断。若沿用等距内收(横半径只比
|
|
150
|
+
* 遮罩小一个 inset),轨道就跟着遮罩一起铺得很宽、两端贴着播放器左右下角擦出去,
|
|
151
|
+
* 看着就是「顶到最左最右」。反推见 constants 的 getTrackRxBottom。
|
|
152
|
+
*/
|
|
153
|
+
var trackRxOverride = this._style('trackRx', 0) * contentScale;
|
|
154
|
+
this.rx = trackRxOverride > 0 ? trackRxOverride : this.maskRx - this.trackEdgeInset;
|
|
106
155
|
var fullWidth = this.maskRx * 2; // 完整椭圆的理论宽度
|
|
107
156
|
this.CX = fullWidth / 2; // 椭圆在完整坐标系里水平居中
|
|
108
157
|
this.topSafeArea = this._style('topSafeArea', TOP_SAFE_AREA) * contentScale;
|
|
@@ -129,6 +178,13 @@ class ZoomTurntableRenderer {
|
|
|
129
178
|
// 这里把画布向外扩一圈,绘制坐标同步补偿,视觉不变但整个转盘区域都可拖动。
|
|
130
179
|
this.hitPaddingX = this._style('hitPaddingX', 0) * contentScale;
|
|
131
180
|
this.hitPaddingY = this._style('hitPaddingY', 0) * contentScale;
|
|
181
|
+
// 上下余量可分别覆盖,默认都取 hitPaddingY。
|
|
182
|
+
// 竖屏底部必须传 0:转盘贴着播放器底边,底边以下的余量既摸不到(原生层已裁),
|
|
183
|
+
// 又会把 canvas 底边推到播放器之外 —— 而 CoverView 里负 margin 并不可靠,
|
|
184
|
+
// 一旦不生效,canvas 底边就停在播放器底边,绘制内容整体被顶高一截、下面还留一条缝。
|
|
185
|
+
this.hitPaddingTop = this._style('hitPaddingTop', this._style('hitPaddingY', 0)) * contentScale;
|
|
186
|
+
this.hitPaddingBottom =
|
|
187
|
+
this._style('hitPaddingBottom', this._style('hitPaddingY', 0)) * contentScale;
|
|
132
188
|
|
|
133
189
|
this.viewportLeft = this.CX - cropHalfWidth;
|
|
134
190
|
this.viewportTop = this.topSafeArea;
|
|
@@ -138,8 +194,11 @@ class ZoomTurntableRenderer {
|
|
|
138
194
|
this.drawWidth = this.viewportRight - this.viewportLeft;
|
|
139
195
|
this.drawHeight = this.viewportBottom - this.viewportTop;
|
|
140
196
|
// 画布尺寸 = 椭圆截面 + 触摸响应余量(两侧 / 上下各扩一份)
|
|
141
|
-
|
|
142
|
-
|
|
197
|
+
// 向上取整:截面宽高是浮点,原生层按整数像素布局,取整不足会把最外侧那一行像素裁掉,
|
|
198
|
+
// 表现就是「有些机型边缘露 1px 空白」。取整后 canvas 略大于内容,边缘那点空隙由
|
|
199
|
+
// 遮罩的 EDGE_OVERFILL 铺满。**必须与 index.js 的 computeCanvasViewport 用同一套公式**。
|
|
200
|
+
this.baseWidth = Math.ceil(this.drawWidth + this.hitPaddingX * 2);
|
|
201
|
+
this.baseHeight = Math.ceil(this.drawHeight + this.hitPaddingTop + this.hitPaddingBottom);
|
|
143
202
|
// 横屏侧边展示时,绘制坐标系先整体旋转 90 度,所以 canvas 宽高也要交换。
|
|
144
203
|
this.WIDTH = this.orientation === 'right' ? this.baseHeight : this.baseWidth;
|
|
145
204
|
this.HEIGHT = this.orientation === 'right' ? this.baseWidth : this.baseHeight;
|
|
@@ -259,7 +318,7 @@ class ZoomTurntableRenderer {
|
|
|
259
318
|
_render() {
|
|
260
319
|
var ctx = this.ctx;
|
|
261
320
|
var s = this.scale;
|
|
262
|
-
var geo = s *
|
|
321
|
+
var geo = s * GEO_SCALE;
|
|
263
322
|
ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
|
264
323
|
ctx.save();
|
|
265
324
|
if (this.orientation === 'right') {
|
|
@@ -272,7 +331,7 @@ class ZoomTurntableRenderer {
|
|
|
272
331
|
// 画布因触摸余量向外扩过,绘制原点要补偿回来,保证视觉位置不变
|
|
273
332
|
ctx.translate(
|
|
274
333
|
-this.viewportLeft + (this.hitPaddingX || 0),
|
|
275
|
-
-this.viewportTop + (this.
|
|
334
|
+
-this.viewportTop + (this.hitPaddingTop || 0) + (this.contentOffsetY || 0)
|
|
276
335
|
);
|
|
277
336
|
|
|
278
337
|
// 刻度盘角度位移:让当前 progress 对应的刻度转到指针处。
|
|
@@ -303,8 +362,10 @@ class ZoomTurntableRenderer {
|
|
|
303
362
|
this.maskVisibleEndAngle * DEG_TO_RAD,
|
|
304
363
|
false
|
|
305
364
|
);
|
|
306
|
-
|
|
307
|
-
|
|
365
|
+
// 平底边多铺 EDGE_OVERFILL:这条边在竖屏落在播放器底边、横屏落在屏幕右缘,
|
|
366
|
+
// 亚像素舍入下差一点就露出一条 1px 的画面缝,多铺一点由画布边界裁掉,观感无差别。
|
|
367
|
+
ctx.lineTo(this.viewportRight, this.viewportBottom + EDGE_OVERFILL);
|
|
368
|
+
ctx.lineTo(this.viewportLeft, this.viewportBottom + EDGE_OVERFILL);
|
|
308
369
|
ctx.closePath();
|
|
309
370
|
ctx.fillStyle = 'rgba(0,0,0,' + this._style('overlayOpacity', 0.44) + ')';
|
|
310
371
|
ctx.fill();
|
|
@@ -325,13 +386,33 @@ class ZoomTurntableRenderer {
|
|
|
325
386
|
var dotRx = this.rx; // 刻度点所在椭圆的横半径
|
|
326
387
|
var dotRy = this.ry; // 刻度点所在椭圆的纵半径
|
|
327
388
|
var isRightOrientation = this.orientation === 'right'; // 是否为横屏侧边模式
|
|
389
|
+
var markFontSize = this._style('markLabelFontSize', 14) * s; // 刻度字号
|
|
390
|
+
var currentFontSize =
|
|
391
|
+
this._style('currentLabelFontSize', this._style('markLabelFontSize', 14)) * s; // 当前值字号
|
|
392
|
+
/**
|
|
393
|
+
* 文案「顶边到遮罩弧」的固定留白(scale=1 时的 px),传了才启用。
|
|
394
|
+
*
|
|
395
|
+
* 竖屏背景带很矮,文案位置必须以遮罩弧为基准 —— 原来按轨道弧往外推 18px,
|
|
396
|
+
* 一旦调整轨道间距(trackEdgeInset)文案就跟着上下窜,还会两种字号各错开一点。
|
|
397
|
+
* 这里改成让文案椭圆与遮罩椭圆**等距内收**:内收量 = 留白 + 半个字高 + 绘制时的 2px 上移补偿,
|
|
398
|
+
* 于是不同字号的文案顶边落在同一条线上、且沿整条弧都与背景边缘保持同样的留白。
|
|
399
|
+
*/
|
|
400
|
+
var labelTopGap = this._style('labelTopGap', -1);
|
|
401
|
+
var markLabelInset =
|
|
402
|
+
labelTopGap >= 0 ? labelTopGap * s + markFontSize / 2 + 2 * s : null;
|
|
403
|
+
var currentLabelInset =
|
|
404
|
+
labelTopGap >= 0 ? labelTopGap * s + currentFontSize / 2 + 2 * s : null;
|
|
328
405
|
// 横屏侧边模式时,标签朝椭圆内侧收;底部模式则往外侧展开一点,保证和弧线拉开距离。
|
|
329
406
|
var labelRx = isRightOrientation
|
|
330
407
|
? this.rx - this._style('innerLabelOffsetX', 22) * geo
|
|
331
|
-
:
|
|
408
|
+
: markLabelInset != null
|
|
409
|
+
? this.maskRx - markLabelInset
|
|
410
|
+
: this.rx + 4 * geo;
|
|
332
411
|
var labelRy = isRightOrientation
|
|
333
412
|
? this.ry - this._style('innerLabelOffsetY', 22) * geo
|
|
334
|
-
:
|
|
413
|
+
: markLabelInset != null
|
|
414
|
+
? this.maskRy - markLabelInset
|
|
415
|
+
: this.ry + 18 * geo;
|
|
335
416
|
var marks = this.marks || [];
|
|
336
417
|
if (marks.length > 0) {
|
|
337
418
|
ctx.textAlign = 'center';
|
|
@@ -353,7 +434,7 @@ class ZoomTurntableRenderer {
|
|
|
353
434
|
var lx = this.CX + labelRx * Math.cos(mRad); // 刻度文字 x 坐标
|
|
354
435
|
var ly = this.CY + labelRy * Math.sin(mRad); // 刻度文字 y 坐标
|
|
355
436
|
ctx.fillStyle = 'rgba(255,255,255,' + (0.76 - Math.abs(progressOffset) * 1.5) + ')';
|
|
356
|
-
ctx.font = '500 ' +
|
|
437
|
+
ctx.font = '500 ' + markFontSize + 'px sans-serif';
|
|
357
438
|
this._fillText(this._normalizeLabel(marks[k].label, marks[k].value), lx, ly - 2 * s);
|
|
358
439
|
}
|
|
359
440
|
}
|
|
@@ -362,10 +443,18 @@ class ZoomTurntableRenderer {
|
|
|
362
443
|
var pointerRad = POINTER_ANGLE * DEG_TO_RAD; // 指针在椭圆上的固定角度
|
|
363
444
|
var px = this.CX + this.rx * Math.cos(pointerRad); // 指针圆环中心 x
|
|
364
445
|
var py = this.CY + this.ry * Math.sin(pointerRad); // 指针圆环中心 y
|
|
365
|
-
//
|
|
366
|
-
|
|
446
|
+
// 当前值文字与普通刻度共用同一套基准:启用 labelTopGap 时各按自己的字号算内收量,
|
|
447
|
+
// 两种字号的顶边因此对齐同一条线;没启用时沿用旧的 labelRx/labelRy。
|
|
448
|
+
var currentLabelRx =
|
|
449
|
+
!isRightOrientation && currentLabelInset != null
|
|
450
|
+
? this.maskRx - currentLabelInset
|
|
451
|
+
: labelRx;
|
|
367
452
|
// 横屏右侧时当前值文字再往内侧让一点,避免和固定光标圆环重合。
|
|
368
|
-
var currentLabelRy = isRightOrientation
|
|
453
|
+
var currentLabelRy = isRightOrientation
|
|
454
|
+
? labelRy - 10 * geo
|
|
455
|
+
: currentLabelInset != null
|
|
456
|
+
? this.maskRy - currentLabelInset
|
|
457
|
+
: labelRy;
|
|
369
458
|
var currentLabelX = this.CX + currentLabelRx * Math.cos(pointerRad); // 当前值文字 x
|
|
370
459
|
var currentLabelY = this.CY + currentLabelRy * Math.sin(pointerRad); // 当前值文字 y
|
|
371
460
|
ctx.beginPath();
|
|
@@ -381,7 +470,7 @@ class ZoomTurntableRenderer {
|
|
|
381
470
|
ctx.textAlign = 'center';
|
|
382
471
|
ctx.textBaseline = 'middle';
|
|
383
472
|
ctx.fillStyle = this.activeColor;
|
|
384
|
-
ctx.font = '700 ' +
|
|
473
|
+
ctx.font = '700 ' + currentFontSize + 'px sans-serif';
|
|
385
474
|
this._fillText(this._formatValue(this.value), currentLabelX, currentLabelY - 2 * s);
|
|
386
475
|
ctx.restore();
|
|
387
476
|
}
|
|
@@ -457,6 +546,10 @@ class ZoomTurntableRenderer {
|
|
|
457
546
|
var prevTopSafeArea = this._style('topSafeArea', TOP_SAFE_AREA); // 上一次顶部安全区
|
|
458
547
|
var prevHitPaddingX = this._style('hitPaddingX', 0); // 上一次横向触摸余量
|
|
459
548
|
var prevHitPaddingY = this._style('hitPaddingY', 0); // 上一次纵向触摸余量
|
|
549
|
+
var prevHitPaddingTop = this._style('hitPaddingTop', prevHitPaddingY); // 上一次上余量
|
|
550
|
+
var prevHitPaddingBottom = this._style('hitPaddingBottom', prevHitPaddingY); // 上一次下余量
|
|
551
|
+
var prevEllipseRx = this._style('ellipseRx', BASE_RX); // 上一次椭圆横半径
|
|
552
|
+
var prevEllipseRy = this._style('ellipseRy', BASE_RY); // 上一次椭圆纵半径
|
|
460
553
|
if (options.min != null) this.min = options.min;
|
|
461
554
|
if (options.max != null) this.max = options.max;
|
|
462
555
|
if (options.step != null) this.step = options.step;
|
|
@@ -475,7 +568,11 @@ class ZoomTurntableRenderer {
|
|
|
475
568
|
this.cropRatio !== prevCropRatio ||
|
|
476
569
|
nextTopSafeArea !== prevTopSafeArea ||
|
|
477
570
|
this._style('hitPaddingX', 0) !== prevHitPaddingX ||
|
|
478
|
-
this._style('hitPaddingY', 0) !== prevHitPaddingY
|
|
571
|
+
this._style('hitPaddingY', 0) !== prevHitPaddingY ||
|
|
572
|
+
this._style('hitPaddingTop', this._style('hitPaddingY', 0)) !== prevHitPaddingTop ||
|
|
573
|
+
this._style('hitPaddingBottom', this._style('hitPaddingY', 0)) !== prevHitPaddingBottom ||
|
|
574
|
+
this._style('ellipseRx', BASE_RX) !== prevEllipseRx ||
|
|
575
|
+
this._style('ellipseRy', BASE_RY) !== prevEllipseRy;
|
|
479
576
|
// 只有尺寸相关参数变化才重设 canvas;字号、线宽等普通样式变化只重绘,避免拖动时闪屏。
|
|
480
577
|
if (shouldResetCanvas) {
|
|
481
578
|
var nextScale = options.scale != null ? options.scale : this.inputScale;
|
|
@@ -484,9 +581,10 @@ class ZoomTurntableRenderer {
|
|
|
484
581
|
}
|
|
485
582
|
// 以下两项不改变 canvas 尺寸,走不到上面的重算分支,这里单独同步后重绘即可
|
|
486
583
|
this.contentOffsetY = this._style('contentOffsetY', 0) * this.scale;
|
|
487
|
-
this.trackEdgeInset = this._style('trackEdgeInset',
|
|
488
|
-
this.
|
|
489
|
-
|
|
584
|
+
this.trackEdgeInset = this._style('trackEdgeInset', DEFAULT_TRACK_EDGE_INSET) * this.scale;
|
|
585
|
+
this.ry = this.maskRy - this.trackEdgeInset;
|
|
586
|
+
var nextTrackRx = this._style('trackRx', 0) * this.scale;
|
|
587
|
+
this.rx = nextTrackRx > 0 ? nextTrackRx : this.maskRx - this.trackEdgeInset;
|
|
490
588
|
this.value = Math.max(this.min, Math.min(this.max, this.value));
|
|
491
589
|
this.progress = this.getProgress(this.value);
|
|
492
590
|
this._buildTicks();
|
|
@@ -527,6 +625,8 @@ class ZoomTurntableRenderer {
|
|
|
527
625
|
this.lastTouchPoint = this.orientation === 'right'
|
|
528
626
|
? touch.clientY - rectTop
|
|
529
627
|
: touch.clientX - rectLeft;
|
|
628
|
+
// tap 判定的起点:位移 / 时长都在阈值内,touchend 时按「点击刻度」处理
|
|
629
|
+
this._tapStart = { x: touch.clientX, y: touch.clientY, t: Date.now(), moved: false };
|
|
530
630
|
dlog('touchstart lastTouchPoint=' + this.lastTouchPoint, 'value=' + this.value);
|
|
531
631
|
}
|
|
532
632
|
|
|
@@ -549,9 +649,20 @@ class ZoomTurntableRenderer {
|
|
|
549
649
|
? touch.clientY - rectTop
|
|
550
650
|
: touch.clientX - rectLeft;
|
|
551
651
|
|
|
652
|
+
if (this._tapStart && !this._tapStart.moved) {
|
|
653
|
+
if (
|
|
654
|
+
Math.abs(touch.clientX - this._tapStart.x) > TAP_MOVE_TOLERANCE ||
|
|
655
|
+
Math.abs(touch.clientY - this._tapStart.y) > TAP_MOVE_TOLERANCE
|
|
656
|
+
) {
|
|
657
|
+
this._tapStart.moved = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
552
661
|
// 像素增量直接映射到「值」的等量变化,而非 progress;
|
|
553
662
|
// 这样无论处于高倍率还是低倍率端,等量滑动产生等量值变化,手感一致(消除 log 曲线导致的两端灵敏度差异)。
|
|
554
|
-
|
|
663
|
+
// 本次拖动的像素增量(含方向修正 + 横屏的轴向翻转)
|
|
664
|
+
var axisSign = this.orientation === 'right' ? RIGHT_AXIS_SIGN : 1;
|
|
665
|
+
var deltaX = (currentPoint - this.lastTouchPoint) * DRAG_DIRECTION * axisSign;
|
|
555
666
|
this.lastTouchPoint = currentPoint;
|
|
556
667
|
var valueSpan = this.max - this.min; // 整个倍率区间跨度
|
|
557
668
|
var pixelsForFullSpan = 300 * this.scale; // 走完整个区间大约需要多少像素
|
|
@@ -579,19 +690,131 @@ class ZoomTurntableRenderer {
|
|
|
579
690
|
this._scheduleRender();
|
|
580
691
|
}
|
|
581
692
|
|
|
582
|
-
_handTouchEnd() {
|
|
693
|
+
_handTouchEnd(e) {
|
|
583
694
|
dlog('touchend/cancel 触发', 'dragging=' + this.dragging, 'value=' + this.value);
|
|
584
|
-
|
|
585
|
-
|
|
695
|
+
var wasDragging = this.dragging;
|
|
696
|
+
this.dragging = false;
|
|
697
|
+
|
|
698
|
+
// 没怎么移动、按得也短 —— 当作「点击某个刻度」,直接滚过去并下发
|
|
699
|
+
var tapped = false;
|
|
700
|
+
var isTap = !!(wasDragging && this._tapStart && !this._tapStart.moved &&
|
|
701
|
+
Date.now() - this._tapStart.t <= TAP_MAX_MS);
|
|
702
|
+
if (isTap) {
|
|
703
|
+
var touch = e && ((e.changedTouches && e.changedTouches[0]) || (e.touches && e.touches[0]));
|
|
704
|
+
if (touch) {
|
|
705
|
+
tapped = this._handleTapAt(touch.clientX, touch.clientY);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this._tapStart = null;
|
|
709
|
+
|
|
710
|
+
if (wasDragging) {
|
|
586
711
|
this._cachedRect = null;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
712
|
+
if (isTap && !tapped) {
|
|
713
|
+
/**
|
|
714
|
+
* 点在转盘的空白处(没命中刻度带)—— 用户的意图就是「点画面」。
|
|
715
|
+
*
|
|
716
|
+
* canvas 覆盖了播放器下部很大一片,touchstart/end 又是 preventDefault + stopPropagation,
|
|
717
|
+
* 播放器根本收不到这一下 tap,于是「点画面收起转盘」在转盘范围内整个失效,
|
|
718
|
+
* 表现就是「第一次点没反应、第二次点(落到画面上部)才收起」。
|
|
719
|
+
* 这里补一条回调,让逻辑层把它当作点画面处理。
|
|
720
|
+
*
|
|
721
|
+
* 同时不发 onChangeEnd:值没变,再下发一次纯属多余。
|
|
722
|
+
*/
|
|
723
|
+
this.callMethod('onBlankTap');
|
|
724
|
+
} else if (!tapped) {
|
|
725
|
+
// tap 命中时由动画结束统一发 onChange / onChangeEnd,这里不能再按旧值发一次;
|
|
726
|
+
// 300ms 冷却期,吸收跨线程残余的 onChange 回写,避免松手后闪动
|
|
727
|
+
this._cooldownUntil = Date.now() + 300;
|
|
728
|
+
this.callMethod('onChangeEnd', this.value);
|
|
729
|
+
}
|
|
590
730
|
}
|
|
591
731
|
// 无论是否处于拖动,都要恢复原生手势,避免异常路径下 nativeDisabled 卡在 true
|
|
592
732
|
this.callMethod('onTouchEnd');
|
|
593
733
|
}
|
|
594
734
|
|
|
735
|
+
/**
|
|
736
|
+
* canvas 内坐标 → 绘制坐标系(逆掉 _render 里的 translate / rotate)。
|
|
737
|
+
* 竖屏只有一次平移;横屏画布整体旋转过 -90°,要连旋转一起逆算。
|
|
738
|
+
*/
|
|
739
|
+
_drawPointFromCanvas(cx, cy) {
|
|
740
|
+
var offX = -this.viewportLeft + (this.hitPaddingX || 0);
|
|
741
|
+
var offY = -this.viewportTop + (this.hitPaddingTop || 0) + (this.contentOffsetY || 0);
|
|
742
|
+
if (this.orientation === 'right') {
|
|
743
|
+
// 正变换:canvas = ( py + offY, baseWidth - (px + offX) )
|
|
744
|
+
return { x: this.baseWidth - cy - offX, y: cx - offY };
|
|
745
|
+
}
|
|
746
|
+
return { x: cx - offX, y: cy - offY };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* 点击刻度:由触点反推该处对应的倍率。
|
|
751
|
+
*
|
|
752
|
+
* 刻度盘整体旋转 rotation = POINTER_ANGLE - getAngle(progress),所以某个 p 的绘制角
|
|
753
|
+
* angle(p) = POINTER_ANGLE + (p - progress) * ARC_SPAN
|
|
754
|
+
* 反过来由触点的椭圆参数角 θ 就能解出 p:
|
|
755
|
+
* p = progress + (θ - POINTER_ANGLE) / ARC_SPAN
|
|
756
|
+
*
|
|
757
|
+
* @returns {boolean} 是否命中刻度带(未命中则按普通 touchend 走)
|
|
758
|
+
*/
|
|
759
|
+
_handleTapAt(clientX, clientY) {
|
|
760
|
+
var rect = this._cachedRect;
|
|
761
|
+
if (!rect) return false;
|
|
762
|
+
var pt = this._drawPointFromCanvas(clientX - rect.left, clientY - rect.top);
|
|
763
|
+
// 归一化到单位圆:半径 1 即刻度轨道所在的椭圆
|
|
764
|
+
var nx = (pt.x - this.CX) / this.rx;
|
|
765
|
+
var ny = (pt.y - this.CY) / this.ry;
|
|
766
|
+
var r = Math.sqrt(nx * nx + ny * ny);
|
|
767
|
+
if (r < TAP_HIT_MIN_R || r > TAP_HIT_MAX_R) {
|
|
768
|
+
dlog('tap 未命中刻度带,忽略', 'r=' + r.toFixed(2));
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
var theta = Math.atan2(ny, nx) * RAD_TO_DEG;
|
|
772
|
+
if (theta < 0) theta += 360; // 上半椭圆对应 [180, 360]
|
|
773
|
+
if (theta < START_ANGLE || theta > END_ANGLE) {
|
|
774
|
+
dlog('tap 角度超出弧段,忽略', 'theta=' + theta.toFixed(1));
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
var p = this.progress + (theta - POINTER_ANGLE) / ARC_SPAN;
|
|
778
|
+
if (p < 0) p = 0;
|
|
779
|
+
else if (p > 1) p = 1;
|
|
780
|
+
var target = this._snapValue(this._valueFromProgress(p));
|
|
781
|
+
dlog('tap 命中', 'theta=' + theta.toFixed(1), 'r=' + r.toFixed(2), 'target=' + target);
|
|
782
|
+
// 点的就是当前值:算命中(不再按旧值重复下发),但不用动
|
|
783
|
+
if (Math.abs(target - this.value) < this.step * 0.5) return true;
|
|
784
|
+
this._animateTo(target);
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** 点击后滚到目标刻度:动画期间只重绘,落位后才发一次 onChange + onChangeEnd */
|
|
789
|
+
_animateTo(target) {
|
|
790
|
+
var self = this;
|
|
791
|
+
var from = this.value;
|
|
792
|
+
var startAt = Date.now();
|
|
793
|
+
var raf = this._getRaf();
|
|
794
|
+
// 动画期间屏蔽外部受控回写,否则会被 value 回写打断
|
|
795
|
+
this._cooldownUntil = startAt + TAP_ANIM_MS + 300;
|
|
796
|
+
var tick = function () {
|
|
797
|
+
var t = (Date.now() - startAt) / TAP_ANIM_MS;
|
|
798
|
+
if (t > 1) t = 1;
|
|
799
|
+
var eased = 1 - Math.pow(1 - t, 3); // ease-out cubic
|
|
800
|
+
var v = from + (target - from) * eased;
|
|
801
|
+
self.value = self._snapValue(v);
|
|
802
|
+
self.progress = self.getProgress(self.value);
|
|
803
|
+
self._scheduleRender();
|
|
804
|
+
if (t < 1) {
|
|
805
|
+
raf(tick);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
self.value = target;
|
|
809
|
+
self.progress = self.getProgress(target);
|
|
810
|
+
self._scheduleRender();
|
|
811
|
+
self._cooldownUntil = Date.now() + 300;
|
|
812
|
+
self.callMethod('onChange', target);
|
|
813
|
+
self.callMethod('onChangeEnd', target);
|
|
814
|
+
};
|
|
815
|
+
raf(tick);
|
|
816
|
+
}
|
|
817
|
+
|
|
595
818
|
setupEventListeners() {
|
|
596
819
|
dlog(
|
|
597
820
|
'setupEventListeners',
|
|
@@ -608,12 +831,18 @@ class ZoomTurntableRenderer {
|
|
|
608
831
|
this.handTouchStart = this._handTouchStart.bind(this);
|
|
609
832
|
this.handTouchMove = this._handTouchMove.bind(this);
|
|
610
833
|
this.handTouchEnd = this._handTouchEnd.bind(this);
|
|
834
|
+
// 手势被系统打断走的是 cancel,不能当作「点击刻度」,先把 tap 起点清掉再收尾
|
|
835
|
+
var self = this;
|
|
836
|
+
this.handTouchCancel = function (e) {
|
|
837
|
+
self._tapStart = null;
|
|
838
|
+
self._handTouchEnd(e);
|
|
839
|
+
};
|
|
611
840
|
this.canvas.addEventListener('touchstart', this.handTouchStart, false);
|
|
612
841
|
this.canvas.addEventListener('touchmove', this.handTouchMove, false);
|
|
613
842
|
this.canvas.addEventListener('touchend', this.handTouchEnd, false);
|
|
614
843
|
// 手势被系统打断时 touchend 不会派发,必须补 touchcancel,
|
|
615
844
|
// 否则 ty.nativeDisabled 会卡在 true,之后播放器所有原生手势都失效
|
|
616
|
-
this.canvas.addEventListener('touchcancel', this.
|
|
845
|
+
this.canvas.addEventListener('touchcancel', this.handTouchCancel, false);
|
|
617
846
|
}
|
|
618
847
|
|
|
619
848
|
destroy() {
|
|
@@ -627,7 +856,7 @@ class ZoomTurntableRenderer {
|
|
|
627
856
|
this.canvas.removeEventListener('touchstart', this.handTouchStart, false);
|
|
628
857
|
this.canvas.removeEventListener('touchmove', this.handTouchMove, false);
|
|
629
858
|
this.canvas.removeEventListener('touchend', this.handTouchEnd, false);
|
|
630
|
-
this.canvas.removeEventListener('touchcancel', this.
|
|
859
|
+
this.canvas.removeEventListener('touchcancel', this.handTouchCancel, false);
|
|
631
860
|
}
|
|
632
861
|
}
|
|
633
862
|
}
|
|
@@ -45,11 +45,44 @@ export interface ZoomTurntableStyleOptions {
|
|
|
45
45
|
hitPaddingX?: number;
|
|
46
46
|
hitPaddingY?: number;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* 纵向触摸余量的上 / 下单独覆盖,scale=1 时的 px 值,默认都取 hitPaddingY。
|
|
49
|
+
* 竖屏底部传 0:canvas 底边即播放器底边,底边以下的余量收不到 touch(原生层已裁),
|
|
50
|
+
* 留着只会把 canvas 推出播放器,从而依赖 CoverView 里并不可靠的负 margin。
|
|
51
|
+
*/
|
|
52
|
+
hitPaddingTop?: number;
|
|
53
|
+
hitPaddingBottom?: number;
|
|
54
|
+
/**
|
|
55
|
+
* 轨道(含刻度点、指针)与标记文字向椭圆内侧等距收的距离,
|
|
56
|
+
* scale=1 时的 px 值,默认 30。横半径与纵半径各减同一个绝对值,
|
|
57
|
+
* 这样内部虚线弧与外圈遮罩弧的间距处处相等、弧度看起来一致
|
|
58
|
+
* (按比例内收会让弧顶间距明显小于两侧,外圈显得更扁)。
|
|
50
59
|
* 遮罩半径不变,故不影响组件占位尺寸。
|
|
51
60
|
*/
|
|
52
61
|
trackEdgeInset?: number;
|
|
62
|
+
/**
|
|
63
|
+
* 轨道椭圆的横半径,scale=1 时的 px 值。不传则与纵向一样按 trackEdgeInset 等距内收。
|
|
64
|
+
* 传一个更小的值可让虚线弧比遮罩弧更弯 —— 两端在到达播放器左右边缘之前就降到
|
|
65
|
+
* 底边以下、被画布底边切断,也就是设计稿里「刻度线扎进播放器底部」的样子。
|
|
66
|
+
*/
|
|
67
|
+
trackRx?: number;
|
|
68
|
+
/**
|
|
69
|
+
* 刻度文案「顶边到遮罩弧」的留白,scale=1 时的 px 值。不传则沿用旧算法
|
|
70
|
+
* (按轨道弧往外推 18px)。传了之后文案椭圆与遮罩椭圆等距内收,
|
|
71
|
+
* 不同字号的顶边对齐同一条线,且沿整条弧留白一致 —— 仅 `bottom` 模式生效。
|
|
72
|
+
*/
|
|
73
|
+
labelTopGap?: number;
|
|
74
|
+
/**
|
|
75
|
+
* 遮罩椭圆的横半径,scale=1 时的 px 值,默认 258(设计稿 516×351)。
|
|
76
|
+
* 弧顶到屏幕两侧的落差取决于「屏幕半宽 / 横半径」,所以竖屏要跟着窗口宽度缩放,
|
|
77
|
+
* 否则宽屏机型上弧会明显更弯。
|
|
78
|
+
*/
|
|
79
|
+
ellipseRx?: number;
|
|
80
|
+
/**
|
|
81
|
+
* 遮罩椭圆的纵半径,scale=1 时的 px 值,默认 175.5(设计稿 516×351)。
|
|
82
|
+
* 只压扁纵向、不动横向:竖屏背景带高度受限(约 70px)时按比例缩小整只椭圆
|
|
83
|
+
* 会让宽度撑不满播放器,两侧露出直角缺口。
|
|
84
|
+
*/
|
|
85
|
+
ellipseRy?: number;
|
|
53
86
|
}
|
|
54
87
|
export interface ZoomTurntableProps {
|
|
55
88
|
/** 整体缩放倍数,默认 1。1.0 = 完整椭圆 351×516,可见画布约 351×149;椭圆比例固定 351:516 */
|
|
@@ -76,6 +109,12 @@ export interface ZoomTurntableProps {
|
|
|
76
109
|
onTouchStart?: () => void;
|
|
77
110
|
/** 手指离开转盘,含 touchcancel(用于恢复原生手势) */
|
|
78
111
|
onTouchEnd?: () => void;
|
|
112
|
+
/**
|
|
113
|
+
* 点在转盘空白处(未命中刻度带)。
|
|
114
|
+
* canvas 会吃掉落在自己范围内的 tap,播放器收不到,「点画面收起」在转盘上就失效了;
|
|
115
|
+
* 挂载方拿这个回调当作「点画面」处理即可。
|
|
116
|
+
*/
|
|
117
|
+
onBlankTap?: () => void;
|
|
79
118
|
/** 主刻度标记数组 */
|
|
80
119
|
marks?: ZoomMark[];
|
|
81
120
|
/**
|
|
@@ -16,6 +16,8 @@ export declare const useLensZoom: (options: Options) => {
|
|
|
16
16
|
/** 镜头列表(按 value 升序),用于渲染倍率 tab */
|
|
17
17
|
lenses: import("./lensSkill").Lens[];
|
|
18
18
|
lensIndex: number;
|
|
19
|
+
/** 正在切往的目标镜头下标;-1 表示没有切换进行中 */
|
|
20
|
+
switchingIndex: number;
|
|
19
21
|
/** 画面绝对倍率 */
|
|
20
22
|
curM: number;
|
|
21
23
|
min: number;
|
|
@@ -29,6 +31,7 @@ export declare const useLensZoom: (options: Options) => {
|
|
|
29
31
|
ticks: number[];
|
|
30
32
|
beginDrag: (this: unknown, value: number) => void;
|
|
31
33
|
applyZoom: (this: unknown, targetM: number) => Promise<void>;
|
|
34
|
+
endDrag: (this: unknown) => void;
|
|
32
35
|
switchToLens: (this: unknown, index: number) => Promise<void>;
|
|
33
36
|
};
|
|
34
37
|
export {};
|