-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathTrackAnimation.js
More file actions
407 lines (366 loc) · 14.7 KB
/
Copy pathTrackAnimation.js
File metadata and controls
407 lines (366 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/**
* @fileoverview 百度地图的轨迹播放类,对外开放。
* 实现折线轨迹的“逐段增长”动画,并提供播放控制与变速能力。
*
* @author Baidu Map Api Group
*/
/**
* @namespace BMap的所有library类均放在BMapLib命名空间下
*/
var BMapLib = window.BMapLib = BMapLib || {};
(function () {
var DEFAULT_DURATION = 10000;
var DEFAULT_DELAY = 0;
var DEFAULT_OVERALLVIEW = false;
var DEFAULT_FOLLOWVIEW = true;
var DEFAULT_FOLLOWVIEW_SMOOTHING = 0.18;
// 视口跟随触发阈值(0~1)
var DEFAULT_FOLLOWVIEW_EDGE_PADDING = 0.28;
var PLAY = 1;
var CANCEL = 2;
var PAUSE = 3;
function now() {
return (window.performance && performance.now) ? performance.now() : Date.now();
}
/**
* @exports TrackAnimation as BMapLib.TrackAnimation
* @constructor
* @param {BMap.Map} map 地图实例
* @param {BMap.Polyline} polyline 折线实例
* @param {Object} opts 配置
* {
* duration: Number 动画时长(ms)
* delay: Number 延迟开始(ms)
* overallView: Boolean 是否在结束时 setViewport 展示整条轨迹(默认false)
* followView: Boolean 播放中是否跟随当前点移动视口(默认true)
* followViewSmoothing: Number 跟随平滑系数(0~1)
* followViewEdgePadding: Number 触发跟随的边缘留白比例(0~1),越大越容易触发跟随(默认0.28)
* followViewPadding: Number(兼容)同 followViewEdgePadding
* onAnimateEnd: Function 动画结束回调
* }
*/
BMapLib.TrackAnimation = function (map, polyline, opts) {
if (!map || !polyline) {
return;
}
this._map = map;
this._polyline = polyline;
this._opts = {
duration: DEFAULT_DURATION,
delay: DEFAULT_DELAY,
overallView: DEFAULT_OVERALLVIEW,
followView: DEFAULT_FOLLOWVIEW,
followViewSmoothing: DEFAULT_FOLLOWVIEW_SMOOTHING,
followViewEdgePadding: DEFAULT_FOLLOWVIEW_EDGE_PADDING,
onAnimateEnd: null
};
this._initOpts(opts);
this._status = CANCEL;
this._timer = null;
this._delayTimer = null;
this._startSeq = 0;
this._startTime = 0; // 动画起点(基准时间)
this._pauseTime = 0; // 暂停累计
this._pauseAt = 0; // 暂停时刻
this._speedFactor = 1; // 1为基准
this._smoothCenterPx = null; // 平滑跟随:像素坐标中心
this._totalPath = this._polyline.getPath() || [];
this._expandPath = [];
this._last2Points = [];
this._buildExpandedPath();
};
BMapLib.TrackAnimation.prototype._initOpts = function (opts) {
if (!opts) return;
for (var p in opts) {
if (opts.hasOwnProperty(p)) {
this._opts[p] = opts[p];
}
}
if (typeof this._opts.duration !== "number" || this._opts.duration <= 0) {
this._opts.duration = DEFAULT_DURATION;
}
if (typeof this._opts.delay !== "number" || this._opts.delay < 0) {
this._opts.delay = DEFAULT_DELAY;
}
if (typeof this._opts.followView !== "boolean") {
this._opts.followView = DEFAULT_FOLLOWVIEW;
}
if (typeof this._opts.followViewSmoothing !== "number" || this._opts.followViewSmoothing <= 0 || this._opts.followViewSmoothing > 1) {
this._opts.followViewSmoothing = DEFAULT_FOLLOWVIEW_SMOOTHING;
}
// 兼容旧字段 followViewPadding
if (typeof this._opts.followViewEdgePadding === "undefined" && typeof this._opts.followViewPadding !== "undefined") {
this._opts.followViewEdgePadding = this._opts.followViewPadding;
}
if (typeof this._opts.followViewEdgePadding !== "number" || this._opts.followViewEdgePadding < 0 || this._opts.followViewEdgePadding > 1) {
this._opts.followViewEdgePadding = DEFAULT_FOLLOWVIEW_EDGE_PADDING;
}
};
/**
* 根据时长扩充路径:按段距离分配插值点,保证总点数接近duration/10
*/
BMapLib.TrackAnimation.prototype._buildExpandedPath = function () {
var path = this._totalPath || [];
if (!path || path.length < 2) {
this._expandPath = path ? path.slice(0) : [];
return;
}
var totalNum = Math.max(2, Math.floor(this._opts.duration / 10));
var length = path.length;
var distances = [];
var totalDistance = 0;
for (var i = 1; i < length; i++) {
var d = this._map.getDistance(path[i - 1], path[i]);
distances.push(d);
totalDistance += d;
}
// 极端情况:所有点重合
if (!totalDistance || !isFinite(totalDistance)) {
this._expandPath = path.slice(0);
return;
}
var remaining = totalNum;
var expand = [path[0]];
for (var j = 1; j < length; j++) {
// 最后一段吃掉剩余,减少误差
var num;
if (j === length - 1) {
num = Math.max(1, remaining);
} else {
num = Math.max(1, Math.round(distances[j - 1] / totalDistance * totalNum));
}
remaining -= num;
expand = expand.concat(this._interpolate(path[j - 1], path[j], num));
}
this._expandPath = expand;
};
/**
* 线性插值
* @param {BMap.Point} start
* @param {BMap.Point} end
* @param {number} num 插值点数量(>=1)
*/
BMapLib.TrackAnimation.prototype._interpolate = function (start, end, num) {
var result = [];
if (!num || num <= 0) return result;
for (var i = 1; i <= num; i++) {
var p = new BMap.Point(
(end.lng - start.lng) / num * i + start.lng,
(end.lat - start.lat) / num * i + start.lat
);
result.push(p);
}
return result;
};
/**
* 启动动画
*/
BMapLib.TrackAnimation.prototype.start = function () {
var me = this;
if (!this._polyline || !this._map) return;
// 刷新路径与插值(允许外部先setPolyline再start)
this._totalPath = this._polyline.getPath() || [];
this._buildExpandedPath();
this.cancel(); // 保证幂等:清理旧状态
var seq = ++this._startSeq;
this._delayTimer = setTimeout(function () {
// 若delay期间被cancel/再次start,则忽略这次启动
if (seq !== me._startSeq) {
return;
}
// 轨迹线从第一个点开始
me._polyline.setPath(me._expandPath.slice(0, 1));
me._status = PLAY;
me._startTime = 0;
me._pauseTime = 0;
me._pauseAt = 0;
me._smoothCenterPx = null;
me._step(now());
}, this._opts.delay);
};
/**
* 终止动画并清理
*/
BMapLib.TrackAnimation.prototype.cancel = function () {
this._clearRAF();
if (this._delayTimer) {
clearTimeout(this._delayTimer);
this._delayTimer = null;
}
this._startSeq++;
this._status = CANCEL;
this._startTime = 0;
this._pauseTime = 0;
this._pauseAt = 0;
this._smoothCenterPx = null;
};
/**
* 暂停动画
*/
BMapLib.TrackAnimation.prototype.pause = function () {
if (this._status !== PLAY) return;
this._clearRAF();
this._status = PAUSE;
this._pauseAt = now();
};
/**
* 继续动画
*/
BMapLib.TrackAnimation.prototype.continue = function () {
if (this._status !== PAUSE) return;
var t = now();
this._pauseTime += (t - this._pauseAt);
this._pauseAt = 0;
this._status = PLAY;
this._step(t);
};
/**
* rAF 驱动的逐步绘制
*/
BMapLib.TrackAnimation.prototype._step = function (timestamp) {
if (this._status === CANCEL) {
this._startTime = 0;
return;
}
if (!this._startTime) {
this._startTime = timestamp;
}
var t = timestamp - this._pauseTime;
var percent = (t - this._startTime) / this._opts.duration;
if (percent < 0) percent = 0;
if (percent > 1) percent = 1;
var end = Math.max(1, Math.round(this._expandPath.length * percent));
var currentPath = this._expandPath.slice(0, end);
this._last2Points = currentPath.slice(-2);
this._polyline.setPath(currentPath);
// 播放中视口跟随:采用“安全区 + 像素空间平滑”避免抖动
if (this._opts.followView && this._map && this._expandPath.length && typeof this._map.pointToPixel === "function" && typeof this._map.pixelToPoint === "function") {
var last = currentPath[currentPath.length - 1];
if (last) {
var size = this._map.getSize && this._map.getSize();
if (size && size.width && size.height) {
var trackPx = this._map.pointToPixel(last);
// 当前视口中心(像素)
var centerPt = this._map.getCenter && this._map.getCenter();
var centerPx = centerPt ? this._map.pointToPixel(centerPt) : null;
if (trackPx && centerPx) {
// 安全区:点在中心一定范围内不触发跟随,减少微抖
var pad = this._opts.followViewEdgePadding;
var halfW = size.width / 2;
var halfH = size.height / 2;
var safeW = halfW * (1 - pad);
var safeH = halfH * (1 - pad);
var dx = trackPx.x - centerPx.x;
var dy = trackPx.y - centerPx.y;
if (Math.abs(dx) > safeW || Math.abs(dy) > safeH) {
if (!this._smoothCenterPx) {
this._smoothCenterPx = {x: centerPx.x, y: centerPx.y};
}
// 目标中心:把点拉回到安全区边缘(而不是直接居中),更稳
var targetCx = centerPx.x;
var targetCy = centerPx.y;
if (dx > safeW) targetCx += (dx - safeW);
if (dx < -safeW) targetCx += (dx + safeW);
if (dy > safeH) targetCy += (dy - safeH);
if (dy < -safeH) targetCy += (dy + safeH);
var a = this._opts.followViewSmoothing;
this._smoothCenterPx.x += (targetCx - this._smoothCenterPx.x) * a;
this._smoothCenterPx.y += (targetCy - this._smoothCenterPx.y) * a;
var newCenter = this._map.pixelToPoint(new BMap.Pixel(this._smoothCenterPx.x, this._smoothCenterPx.y));
if (newCenter && typeof this._map.setCenter === "function") {
this._map.setCenter(newCenter);
}
} else {
// 回到安全区内,释放平滑状态,避免拖尾
this._smoothCenterPx = null;
}
}
}
}
}
if (percent < 1) {
this._timer = this._requestFrame(this._step.bind(this));
} else {
this._startTime = 0;
this._pauseTime = 0;
this._pauseAt = 0;
this._status = CANCEL;
// 结束后展示全量轨迹
if (this._opts.overallView && this._map && this._totalPath && this._totalPath.length >= 2 && this._map.setViewport) {
this._map.setViewport(this._totalPath);
}
typeof this._opts.onAnimateEnd === "function" && this._opts.onAnimateEnd();
}
};
BMapLib.TrackAnimation.prototype._requestFrame = function (cb) {
if (window.requestAnimationFrame) {
return window.requestAnimationFrame(cb);
}
return window.setTimeout(function () { cb(now()); }, 16);
};
BMapLib.TrackAnimation.prototype._cancelFrame = function (id) {
if (window.cancelAnimationFrame) {
window.cancelAnimationFrame(id);
return;
}
window.clearTimeout(id);
};
BMapLib.TrackAnimation.prototype._clearRAF = function () {
if (this._timer) {
this._cancelFrame(this._timer);
this._timer = null;
}
};
/**
* 设置持续时间(ms)
*/
BMapLib.TrackAnimation.prototype.setDuration = function (duration) {
if (typeof duration !== "number" || duration <= 0) return;
this._opts.duration = duration;
this._buildExpandedPath();
};
BMapLib.TrackAnimation.prototype.getDuration = function () {
return this._opts.duration;
};
/**
* 设置速度因子(1为基准,>1加速,<1减速)
* 通过调整duration,并修正当前进度对应的起点时间,保证不断档
*/
BMapLib.TrackAnimation.prototype.setSpeed = function (speedFactor) {
if (typeof speedFactor !== "number" || speedFactor <= 0) return;
var oldDuration = this._opts.duration;
var newDuration = oldDuration * (1 / speedFactor);
// 播放中/暂停中需要保持当前进度不跳变
if ((this._status === PLAY || this._status === PAUSE) && this._startTime) {
var t = (this._status === PAUSE && this._pauseAt) ? this._pauseAt : now();
var effective = t - this._pauseTime;
var percent = (effective - this._startTime) / oldDuration;
if (percent < 0) percent = 0;
if (percent > 1) percent = 1;
this._startTime = effective - percent * newDuration;
}
this._speedFactor = speedFactor;
this.setDuration(newDuration);
};
BMapLib.TrackAnimation.prototype.getSpeed = function () {
return this._speedFactor || 1;
};
/**
* 更新折线(不自动start)
*/
BMapLib.TrackAnimation.prototype.setPolyline = function (polyline) {
if (!polyline) return;
this._polyline = polyline;
this._totalPath = polyline.getPath() || [];
this._buildExpandedPath();
};
BMapLib.TrackAnimation.prototype.getPolyline = function () {
return this._polyline;
};
/**
* 获取最后两个点(用于外部定位/跟随)
*/
BMapLib.TrackAnimation.prototype.getLastPoint = function () {
return this._last2Points.slice(0);
};
})();