@ray-js/ipc-player-integration 0.0.50-beta.2 → 0.0.50-beta.4
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/lib/iconfont/iconfont.css +3 -3
- package/lib/iconfont/iconfont.json +1 -1
- package/lib/iconfont/iconfont.ttf +0 -0
- package/lib/iconfont/iconfont.woff +0 -0
- package/lib/iconfont/iconfont.woff2 +0 -0
- package/lib/ui/ui.js +43 -17
- package/lib/widgets/voiceIntercom/voiceIntercom.js +6 -1
- package/lib/widgets/zoomTurntable/constants.d.ts +204 -39
- package/lib/widgets/zoomTurntable/constants.js +323 -56
- package/lib/widgets/zoomTurntable/lensModel.d.ts +10 -2
- package/lib/widgets/zoomTurntable/lensModel.js +24 -7
- package/lib/widgets/zoomTurntable/lensModel.test.js +20 -8
- package/lib/widgets/zoomTurntable/measureVisible.d.ts +9 -0
- package/lib/widgets/zoomTurntable/measureVisible.js +10 -0
- package/lib/widgets/zoomTurntable/turntable/index.js +5 -0
- package/lib/widgets/zoomTurntable/turntable/rjs/index.js +27 -4
- package/lib/widgets/zoomTurntable/turntable/rjs/index.rjs +132 -32
- package/lib/widgets/zoomTurntable/turntable/type.d.ts +41 -2
- package/lib/widgets/zoomTurntable/useLensZoom.d.ts +1 -0
- package/lib/widgets/zoomTurntable/useLensZoom.js +304 -25
- package/lib/widgets/zoomTurntable/zoomTurntableDock.js +94 -36
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.js +96 -18
- package/lib/widgets/zoomTurntable/zoomTurntableTip.js +34 -22
- package/lib/widgets/zoomTurntable/zoomTurntableTip.less +23 -7
- package/package.json +1 -1
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
-- 小程序原生组件层(rjs Component 壳),运行在 render 线程的 ES5 环境,
|
|
3
3
|
沿用内嵌来源 ray-zoom-turntable 的写法,不套用主工程的 ES6+ 规则 */
|
|
4
4
|
import Render from './index.rjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 注意:下面的 computeCanvasViewport 是 canvas **DOM 尺寸**的唯一来源(index.tyml 内联 style 用它),
|
|
8
|
+
* 与 index.rjs 的 _computeGeometry 是两份独立实现,**必须逐项对齐**。
|
|
9
|
+
* 任何影响画布尺寸的 styleOptions(ellipseRx/ellipseRy/hitPadding*)两边都要读,
|
|
10
|
+
* 否则绘制坐标系与 DOM 尺寸不一致:画面被拉伸,且溢出承载 CoverView 的部分收不到 touch。
|
|
11
|
+
*/
|
|
5
12
|
var BASE_RX = 258;
|
|
6
13
|
var BASE_RY = 175.5;
|
|
7
14
|
|
|
@@ -67,7 +74,9 @@ function computeCanvasViewport(props) {
|
|
|
67
74
|
if (cropRatio > 1) cropRatio = 1;
|
|
68
75
|
// 与 renderer 的 hitPadding 保持一致,否则 canvas 的 DOM 尺寸与绘制尺寸会打架
|
|
69
76
|
var hitPaddingX = styleNumber(props.styleOptions, 'hitPaddingX', 0) * inputScale;
|
|
70
|
-
var hitPaddingY = styleNumber(props.styleOptions, 'hitPaddingY', 0)
|
|
77
|
+
var hitPaddingY = styleNumber(props.styleOptions, 'hitPaddingY', 0);
|
|
78
|
+
var hitPaddingTop = styleNumber(props.styleOptions, 'hitPaddingTop', hitPaddingY) * inputScale;
|
|
79
|
+
var hitPaddingBottom = styleNumber(props.styleOptions, 'hitPaddingBottom', hitPaddingY) * inputScale;
|
|
71
80
|
var bottomSin = 2 * cropRatio - 1;
|
|
72
81
|
if (bottomSin < -1) bottomSin = -1;
|
|
73
82
|
if (bottomSin > 1) bottomSin = 1;
|
|
@@ -76,9 +85,19 @@ function computeCanvasViewport(props) {
|
|
|
76
85
|
cropWidthRatio = Math.sqrt(1 - bottomSin * bottomSin);
|
|
77
86
|
}
|
|
78
87
|
var contentScale = inputScale;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
// 椭圆半径同样要读 styleOptions —— 竖屏会按容器 16:9 传入压扁后的 ellipseRx/ellipseRy。
|
|
89
|
+
// 这里漏读过一次:canvas 的 DOM 尺寸仍按设计稿 258×175.5 算(208 高),
|
|
90
|
+
// 而 renderer 按传入值算(186 高),结果画面被拉伸、canvas 顶部溢出承载它的
|
|
91
|
+
// CoverView 被裁掉 —— 裁掉的部分收不到 touch,表现就是「手势整体不生效」。
|
|
92
|
+
var maskRx = styleNumber(props.styleOptions, 'ellipseRx', BASE_RX) * contentScale;
|
|
93
|
+
var maskRy = styleNumber(props.styleOptions, 'ellipseRy', BASE_RY) * contentScale;
|
|
94
|
+
var cropHalfWidth = maskRx * cropWidthRatio;
|
|
95
|
+
|
|
96
|
+
// 向上取整,与 index.rjs 的 baseWidth / baseHeight 保持一致:
|
|
97
|
+
// 尺寸取整不足会被原生层裁掉最外侧那一行像素(部分机型边缘露 1px 空白),
|
|
98
|
+
// 两边算法只要有一处不取整,DOM 尺寸与绘制坐标系就又会打架。
|
|
99
|
+
var canvasWidth = Math.ceil(cropHalfWidth * 2 + hitPaddingX * 2);
|
|
100
|
+
var canvasHeight = Math.ceil(maskRy * 2 * cropRatio + hitPaddingTop + hitPaddingBottom);
|
|
82
101
|
return orientation === 'right' ? {
|
|
83
102
|
canvasWidth: canvasHeight,
|
|
84
103
|
canvasHeight: canvasWidth
|
|
@@ -231,6 +250,10 @@ Component({
|
|
|
231
250
|
onChangeEnd(value) {
|
|
232
251
|
this.triggerEvent('changeEnd', value);
|
|
233
252
|
},
|
|
253
|
+
// 点在转盘空白处(未命中刻度带):等同于点画面,逻辑层据此收起转盘
|
|
254
|
+
onBlankTap() {
|
|
255
|
+
this.triggerEvent('blankTap');
|
|
256
|
+
},
|
|
234
257
|
// 转盘触摸开始 / 结束(含 touchcancel),逻辑层据此切换 ty.nativeDisabled
|
|
235
258
|
onTouchStart() {
|
|
236
259
|
this.triggerEvent('turntableTouchStart');
|
|
@@ -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;
|
|
@@ -41,6 +45,20 @@ const TAP_ANIM_MS = 220;
|
|
|
41
45
|
// 拖动方向:-1 表示「拖动的是转盘本身」——向左拖,盘面左移,右侧的大刻度转到指针处,倍率变大,
|
|
42
46
|
// 与实体相机的调焦环手感一致;+1 则是「拖动指针」的反向手感。
|
|
43
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;
|
|
44
62
|
|
|
45
63
|
class ZoomTurntableRenderer {
|
|
46
64
|
constructor(options) {
|
|
@@ -109,13 +127,31 @@ class ZoomTurntableRenderer {
|
|
|
109
127
|
this.scale = contentScale;
|
|
110
128
|
this.scaleX = contentScale;
|
|
111
129
|
this.scaleY = contentScale;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
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
|
+
// 外圈背景看起来就比内部描边更扁;等距内收才是一对弧度一致的平行弧。
|
|
115
142
|
// 遮罩半径不变,所以 canvas 尺寸与截面都不受影响。
|
|
116
|
-
this.trackEdgeInset = this._style('trackEdgeInset',
|
|
117
|
-
this.
|
|
118
|
-
|
|
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;
|
|
119
155
|
var fullWidth = this.maskRx * 2; // 完整椭圆的理论宽度
|
|
120
156
|
this.CX = fullWidth / 2; // 椭圆在完整坐标系里水平居中
|
|
121
157
|
this.topSafeArea = this._style('topSafeArea', TOP_SAFE_AREA) * contentScale;
|
|
@@ -142,6 +178,13 @@ class ZoomTurntableRenderer {
|
|
|
142
178
|
// 这里把画布向外扩一圈,绘制坐标同步补偿,视觉不变但整个转盘区域都可拖动。
|
|
143
179
|
this.hitPaddingX = this._style('hitPaddingX', 0) * contentScale;
|
|
144
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;
|
|
145
188
|
|
|
146
189
|
this.viewportLeft = this.CX - cropHalfWidth;
|
|
147
190
|
this.viewportTop = this.topSafeArea;
|
|
@@ -151,8 +194,11 @@ class ZoomTurntableRenderer {
|
|
|
151
194
|
this.drawWidth = this.viewportRight - this.viewportLeft;
|
|
152
195
|
this.drawHeight = this.viewportBottom - this.viewportTop;
|
|
153
196
|
// 画布尺寸 = 椭圆截面 + 触摸响应余量(两侧 / 上下各扩一份)
|
|
154
|
-
|
|
155
|
-
|
|
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);
|
|
156
202
|
// 横屏侧边展示时,绘制坐标系先整体旋转 90 度,所以 canvas 宽高也要交换。
|
|
157
203
|
this.WIDTH = this.orientation === 'right' ? this.baseHeight : this.baseWidth;
|
|
158
204
|
this.HEIGHT = this.orientation === 'right' ? this.baseWidth : this.baseHeight;
|
|
@@ -272,7 +318,7 @@ class ZoomTurntableRenderer {
|
|
|
272
318
|
_render() {
|
|
273
319
|
var ctx = this.ctx;
|
|
274
320
|
var s = this.scale;
|
|
275
|
-
var geo = s *
|
|
321
|
+
var geo = s * GEO_SCALE;
|
|
276
322
|
ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
|
277
323
|
ctx.save();
|
|
278
324
|
if (this.orientation === 'right') {
|
|
@@ -285,7 +331,7 @@ class ZoomTurntableRenderer {
|
|
|
285
331
|
// 画布因触摸余量向外扩过,绘制原点要补偿回来,保证视觉位置不变
|
|
286
332
|
ctx.translate(
|
|
287
333
|
-this.viewportLeft + (this.hitPaddingX || 0),
|
|
288
|
-
-this.viewportTop + (this.
|
|
334
|
+
-this.viewportTop + (this.hitPaddingTop || 0) + (this.contentOffsetY || 0)
|
|
289
335
|
);
|
|
290
336
|
|
|
291
337
|
// 刻度盘角度位移:让当前 progress 对应的刻度转到指针处。
|
|
@@ -316,8 +362,10 @@ class ZoomTurntableRenderer {
|
|
|
316
362
|
this.maskVisibleEndAngle * DEG_TO_RAD,
|
|
317
363
|
false
|
|
318
364
|
);
|
|
319
|
-
|
|
320
|
-
|
|
365
|
+
// 平底边多铺 EDGE_OVERFILL:这条边在竖屏落在播放器底边、横屏落在屏幕右缘,
|
|
366
|
+
// 亚像素舍入下差一点就露出一条 1px 的画面缝,多铺一点由画布边界裁掉,观感无差别。
|
|
367
|
+
ctx.lineTo(this.viewportRight, this.viewportBottom + EDGE_OVERFILL);
|
|
368
|
+
ctx.lineTo(this.viewportLeft, this.viewportBottom + EDGE_OVERFILL);
|
|
321
369
|
ctx.closePath();
|
|
322
370
|
ctx.fillStyle = 'rgba(0,0,0,' + this._style('overlayOpacity', 0.44) + ')';
|
|
323
371
|
ctx.fill();
|
|
@@ -338,13 +386,33 @@ class ZoomTurntableRenderer {
|
|
|
338
386
|
var dotRx = this.rx; // 刻度点所在椭圆的横半径
|
|
339
387
|
var dotRy = this.ry; // 刻度点所在椭圆的纵半径
|
|
340
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;
|
|
341
405
|
// 横屏侧边模式时,标签朝椭圆内侧收;底部模式则往外侧展开一点,保证和弧线拉开距离。
|
|
342
406
|
var labelRx = isRightOrientation
|
|
343
407
|
? this.rx - this._style('innerLabelOffsetX', 22) * geo
|
|
344
|
-
:
|
|
408
|
+
: markLabelInset != null
|
|
409
|
+
? this.maskRx - markLabelInset
|
|
410
|
+
: this.rx + 4 * geo;
|
|
345
411
|
var labelRy = isRightOrientation
|
|
346
412
|
? this.ry - this._style('innerLabelOffsetY', 22) * geo
|
|
347
|
-
:
|
|
413
|
+
: markLabelInset != null
|
|
414
|
+
? this.maskRy - markLabelInset
|
|
415
|
+
: this.ry + 18 * geo;
|
|
348
416
|
var marks = this.marks || [];
|
|
349
417
|
if (marks.length > 0) {
|
|
350
418
|
ctx.textAlign = 'center';
|
|
@@ -366,7 +434,7 @@ class ZoomTurntableRenderer {
|
|
|
366
434
|
var lx = this.CX + labelRx * Math.cos(mRad); // 刻度文字 x 坐标
|
|
367
435
|
var ly = this.CY + labelRy * Math.sin(mRad); // 刻度文字 y 坐标
|
|
368
436
|
ctx.fillStyle = 'rgba(255,255,255,' + (0.76 - Math.abs(progressOffset) * 1.5) + ')';
|
|
369
|
-
ctx.font = '500 ' +
|
|
437
|
+
ctx.font = '500 ' + markFontSize + 'px sans-serif';
|
|
370
438
|
this._fillText(this._normalizeLabel(marks[k].label, marks[k].value), lx, ly - 2 * s);
|
|
371
439
|
}
|
|
372
440
|
}
|
|
@@ -375,10 +443,18 @@ class ZoomTurntableRenderer {
|
|
|
375
443
|
var pointerRad = POINTER_ANGLE * DEG_TO_RAD; // 指针在椭圆上的固定角度
|
|
376
444
|
var px = this.CX + this.rx * Math.cos(pointerRad); // 指针圆环中心 x
|
|
377
445
|
var py = this.CY + this.ry * Math.sin(pointerRad); // 指针圆环中心 y
|
|
378
|
-
//
|
|
379
|
-
|
|
446
|
+
// 当前值文字与普通刻度共用同一套基准:启用 labelTopGap 时各按自己的字号算内收量,
|
|
447
|
+
// 两种字号的顶边因此对齐同一条线;没启用时沿用旧的 labelRx/labelRy。
|
|
448
|
+
var currentLabelRx =
|
|
449
|
+
!isRightOrientation && currentLabelInset != null
|
|
450
|
+
? this.maskRx - currentLabelInset
|
|
451
|
+
: labelRx;
|
|
380
452
|
// 横屏右侧时当前值文字再往内侧让一点,避免和固定光标圆环重合。
|
|
381
|
-
var currentLabelRy = isRightOrientation
|
|
453
|
+
var currentLabelRy = isRightOrientation
|
|
454
|
+
? labelRy - 10 * geo
|
|
455
|
+
: currentLabelInset != null
|
|
456
|
+
? this.maskRy - currentLabelInset
|
|
457
|
+
: labelRy;
|
|
382
458
|
var currentLabelX = this.CX + currentLabelRx * Math.cos(pointerRad); // 当前值文字 x
|
|
383
459
|
var currentLabelY = this.CY + currentLabelRy * Math.sin(pointerRad); // 当前值文字 y
|
|
384
460
|
ctx.beginPath();
|
|
@@ -394,7 +470,7 @@ class ZoomTurntableRenderer {
|
|
|
394
470
|
ctx.textAlign = 'center';
|
|
395
471
|
ctx.textBaseline = 'middle';
|
|
396
472
|
ctx.fillStyle = this.activeColor;
|
|
397
|
-
ctx.font = '700 ' +
|
|
473
|
+
ctx.font = '700 ' + currentFontSize + 'px sans-serif';
|
|
398
474
|
this._fillText(this._formatValue(this.value), currentLabelX, currentLabelY - 2 * s);
|
|
399
475
|
ctx.restore();
|
|
400
476
|
}
|
|
@@ -470,6 +546,10 @@ class ZoomTurntableRenderer {
|
|
|
470
546
|
var prevTopSafeArea = this._style('topSafeArea', TOP_SAFE_AREA); // 上一次顶部安全区
|
|
471
547
|
var prevHitPaddingX = this._style('hitPaddingX', 0); // 上一次横向触摸余量
|
|
472
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); // 上一次椭圆纵半径
|
|
473
553
|
if (options.min != null) this.min = options.min;
|
|
474
554
|
if (options.max != null) this.max = options.max;
|
|
475
555
|
if (options.step != null) this.step = options.step;
|
|
@@ -488,7 +568,11 @@ class ZoomTurntableRenderer {
|
|
|
488
568
|
this.cropRatio !== prevCropRatio ||
|
|
489
569
|
nextTopSafeArea !== prevTopSafeArea ||
|
|
490
570
|
this._style('hitPaddingX', 0) !== prevHitPaddingX ||
|
|
491
|
-
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;
|
|
492
576
|
// 只有尺寸相关参数变化才重设 canvas;字号、线宽等普通样式变化只重绘,避免拖动时闪屏。
|
|
493
577
|
if (shouldResetCanvas) {
|
|
494
578
|
var nextScale = options.scale != null ? options.scale : this.inputScale;
|
|
@@ -497,9 +581,10 @@ class ZoomTurntableRenderer {
|
|
|
497
581
|
}
|
|
498
582
|
// 以下两项不改变 canvas 尺寸,走不到上面的重算分支,这里单独同步后重绘即可
|
|
499
583
|
this.contentOffsetY = this._style('contentOffsetY', 0) * this.scale;
|
|
500
|
-
this.trackEdgeInset = this._style('trackEdgeInset',
|
|
501
|
-
this.
|
|
502
|
-
|
|
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;
|
|
503
588
|
this.value = Math.max(this.min, Math.min(this.max, this.value));
|
|
504
589
|
this.progress = this.getProgress(this.value);
|
|
505
590
|
this._buildTicks();
|
|
@@ -575,7 +660,9 @@ class ZoomTurntableRenderer {
|
|
|
575
660
|
|
|
576
661
|
// 像素增量直接映射到「值」的等量变化,而非 progress;
|
|
577
662
|
// 这样无论处于高倍率还是低倍率端,等量滑动产生等量值变化,手感一致(消除 log 曲线导致的两端灵敏度差异)。
|
|
578
|
-
|
|
663
|
+
// 本次拖动的像素增量(含方向修正 + 横屏的轴向翻转)
|
|
664
|
+
var axisSign = this.orientation === 'right' ? RIGHT_AXIS_SIGN : 1;
|
|
665
|
+
var deltaX = (currentPoint - this.lastTouchPoint) * DRAG_DIRECTION * axisSign;
|
|
579
666
|
this.lastTouchPoint = currentPoint;
|
|
580
667
|
var valueSpan = this.max - this.min; // 整个倍率区间跨度
|
|
581
668
|
var pixelsForFullSpan = 300 * this.scale; // 走完整个区间大约需要多少像素
|
|
@@ -610,8 +697,9 @@ class ZoomTurntableRenderer {
|
|
|
610
697
|
|
|
611
698
|
// 没怎么移动、按得也短 —— 当作「点击某个刻度」,直接滚过去并下发
|
|
612
699
|
var tapped = false;
|
|
613
|
-
|
|
614
|
-
Date.now() - this._tapStart.t <= TAP_MAX_MS)
|
|
700
|
+
var isTap = !!(wasDragging && this._tapStart && !this._tapStart.moved &&
|
|
701
|
+
Date.now() - this._tapStart.t <= TAP_MAX_MS);
|
|
702
|
+
if (isTap) {
|
|
615
703
|
var touch = e && ((e.changedTouches && e.changedTouches[0]) || (e.touches && e.touches[0]));
|
|
616
704
|
if (touch) {
|
|
617
705
|
tapped = this._handleTapAt(touch.clientX, touch.clientY);
|
|
@@ -621,8 +709,20 @@ class ZoomTurntableRenderer {
|
|
|
621
709
|
|
|
622
710
|
if (wasDragging) {
|
|
623
711
|
this._cachedRect = null;
|
|
624
|
-
|
|
625
|
-
|
|
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,这里不能再按旧值发一次;
|
|
626
726
|
// 300ms 冷却期,吸收跨线程残余的 onChange 回写,避免松手后闪动
|
|
627
727
|
this._cooldownUntil = Date.now() + 300;
|
|
628
728
|
this.callMethod('onChangeEnd', this.value);
|
|
@@ -638,7 +738,7 @@ class ZoomTurntableRenderer {
|
|
|
638
738
|
*/
|
|
639
739
|
_drawPointFromCanvas(cx, cy) {
|
|
640
740
|
var offX = -this.viewportLeft + (this.hitPaddingX || 0);
|
|
641
|
-
var offY = -this.viewportTop + (this.
|
|
741
|
+
var offY = -this.viewportTop + (this.hitPaddingTop || 0) + (this.contentOffsetY || 0);
|
|
642
742
|
if (this.orientation === 'right') {
|
|
643
743
|
// 正变换:canvas = ( py + offY, baseWidth - (px + offX) )
|
|
644
744
|
return { x: this.baseWidth - cy - offX, y: cx - offY };
|
|
@@ -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
|
/**
|
|
@@ -31,6 +31,7 @@ export declare const useLensZoom: (options: Options) => {
|
|
|
31
31
|
ticks: number[];
|
|
32
32
|
beginDrag: (this: unknown, value: number) => void;
|
|
33
33
|
applyZoom: (this: unknown, targetM: number) => Promise<void>;
|
|
34
|
+
endDrag: (this: unknown) => void;
|
|
34
35
|
switchToLens: (this: unknown, index: number) => Promise<void>;
|
|
35
36
|
};
|
|
36
37
|
export {};
|