smartphoto 2.1.3 → 2.1.6
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 +18 -5
- package/css/smartphoto.css +82 -22
- package/css/smartphoto.min.css +1 -1
- package/js/jquery-smartphoto.js +163 -25
- package/js/jquery-smartphoto.min.js +2 -2
- package/js/smartphoto.js +163 -25
- package/js/smartphoto.min.js +2 -2
- package/lib/smartphoto.js +161 -23
- package/lib/smartphoto.mjs +161 -23
- package/lib/types/core/index.d.ts +9 -5
- package/lib/types/core/state.d.ts +6 -6
- package/lib/types/core/types.d.ts +14 -11
- package/lib/types/core/view.d.ts +2 -2
- package/lib/types/index.d.ts +1 -0
- package/package.json +10 -8
- package/css/smartphoto.css.map +0 -1
package/lib/smartphoto.js
CHANGED
|
@@ -124,6 +124,7 @@ var defaults = {
|
|
|
124
124
|
swipeTopToClose: false,
|
|
125
125
|
swipeBottomToClose: true,
|
|
126
126
|
swipeOffset: 100,
|
|
127
|
+
swipeVelocity: 0.5,
|
|
127
128
|
headerHeight: 60,
|
|
128
129
|
footerHeight: 60,
|
|
129
130
|
forceInterval: 10,
|
|
@@ -131,7 +132,7 @@ var defaults = {
|
|
|
131
132
|
loadOffset: 2,
|
|
132
133
|
resizeStyle: "fit",
|
|
133
134
|
lazyAttribute: "data-src",
|
|
134
|
-
animationSpeed:
|
|
135
|
+
animationSpeed: 450
|
|
135
136
|
};
|
|
136
137
|
function deepFreeze(obj) {
|
|
137
138
|
Object.keys(obj).forEach((key) => {
|
|
@@ -145,7 +146,11 @@ function deepFreeze(obj) {
|
|
|
145
146
|
function createState(settings) {
|
|
146
147
|
return {
|
|
147
148
|
options: deepFreeze(
|
|
148
|
-
extend(
|
|
149
|
+
extend(
|
|
150
|
+
{},
|
|
151
|
+
defaults,
|
|
152
|
+
settings
|
|
153
|
+
)
|
|
149
154
|
),
|
|
150
155
|
viewer: {
|
|
151
156
|
isOpen: false,
|
|
@@ -381,6 +386,7 @@ function distance(p1, p2) {
|
|
|
381
386
|
const y = p1.y - p2.y;
|
|
382
387
|
return Math.sqrt(x * x + y * y);
|
|
383
388
|
}
|
|
389
|
+
var MIN_FLICK_DISTANCE = 10;
|
|
384
390
|
function getForceAndTheta(x, y) {
|
|
385
391
|
return { force: Math.sqrt(x * x + y * y), theta: Math.atan2(y, x) };
|
|
386
392
|
}
|
|
@@ -398,6 +404,7 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
398
404
|
let firstPos = null;
|
|
399
405
|
let oldPos = null;
|
|
400
406
|
let moveDir = null;
|
|
407
|
+
let swipeStartTime = 0;
|
|
401
408
|
let photoSwipable = false;
|
|
402
409
|
let firstPhotoPos = null;
|
|
403
410
|
let oldPhotoPos = null;
|
|
@@ -405,6 +412,7 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
405
412
|
let photoVY = 0;
|
|
406
413
|
let pinching = false;
|
|
407
414
|
let oldDistance = 0;
|
|
415
|
+
let pinchMoveFrame = null;
|
|
408
416
|
let vx = 0;
|
|
409
417
|
let vy = 0;
|
|
410
418
|
function isSmartPhone2() {
|
|
@@ -526,6 +534,7 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
526
534
|
dragStart = true;
|
|
527
535
|
firstPos = pos;
|
|
528
536
|
oldPos = pos;
|
|
537
|
+
swipeStartTime = Date.now();
|
|
529
538
|
}
|
|
530
539
|
function startPhotoDrag(e) {
|
|
531
540
|
photoSwipable = true;
|
|
@@ -549,6 +558,23 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
549
558
|
}
|
|
550
559
|
startSwipe(e);
|
|
551
560
|
}
|
|
561
|
+
function scheduleGestureMove() {
|
|
562
|
+
if (pinchMoveFrame !== null) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
pinchMoveFrame = requestAnimationFrame(() => {
|
|
566
|
+
pinchMoveFrame = null;
|
|
567
|
+
callbacks.onGestureMove();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function flushGestureMove() {
|
|
571
|
+
if (pinchMoveFrame === null) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
cancelAnimationFrame(pinchMoveFrame);
|
|
575
|
+
pinchMoveFrame = null;
|
|
576
|
+
callbacks.onGestureMove();
|
|
577
|
+
}
|
|
552
578
|
function movePinch() {
|
|
553
579
|
const points = Array.from(activePointers.values());
|
|
554
580
|
const dist = distance(
|
|
@@ -573,7 +599,7 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
573
599
|
state.viewer.hideUi = state.viewer.scaleSize < 1 || state.viewer.scaleSize > border;
|
|
574
600
|
}
|
|
575
601
|
oldDistance = dist;
|
|
576
|
-
|
|
602
|
+
scheduleGestureMove();
|
|
577
603
|
}
|
|
578
604
|
function moveSwipe(e) {
|
|
579
605
|
const pos = getPos(e);
|
|
@@ -622,6 +648,7 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
622
648
|
}
|
|
623
649
|
function endPinch() {
|
|
624
650
|
pinching = false;
|
|
651
|
+
flushGestureMove();
|
|
625
652
|
const item = currentItem(state);
|
|
626
653
|
if (!item) {
|
|
627
654
|
return;
|
|
@@ -658,9 +685,11 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
658
685
|
const items = currentItems(state) ?? [];
|
|
659
686
|
if (moveDir === "horizontal") {
|
|
660
687
|
let result = "stay";
|
|
661
|
-
|
|
688
|
+
const elapsedMs = Math.max(now - swipeStartTime, 1);
|
|
689
|
+
const isFlick = Math.abs(swipeWidth) >= MIN_FLICK_DISTANCE && Math.abs(swipeWidth) / elapsedMs >= state.options.swipeVelocity;
|
|
690
|
+
if ((swipeWidth >= state.options.swipeOffset || isFlick && swipeWidth > 0) && state.viewer.currentIndex !== 0) {
|
|
662
691
|
result = "prev";
|
|
663
|
-
} else if (swipeWidth <= -state.options.swipeOffset && state.viewer.currentIndex !== items.length - 1) {
|
|
692
|
+
} else if ((swipeWidth <= -state.options.swipeOffset || isFlick && swipeWidth < 0) && state.viewer.currentIndex !== items.length - 1) {
|
|
664
693
|
result = "next";
|
|
665
694
|
}
|
|
666
695
|
callbacks.onSwipeEnd(result);
|
|
@@ -751,6 +780,10 @@ function createGestures({ state, callbacks }, { signal }) {
|
|
|
751
780
|
}
|
|
752
781
|
function detach() {
|
|
753
782
|
clearInterval(interval);
|
|
783
|
+
if (pinchMoveFrame !== null) {
|
|
784
|
+
cancelAnimationFrame(pinchMoveFrame);
|
|
785
|
+
pinchMoveFrame = null;
|
|
786
|
+
}
|
|
754
787
|
}
|
|
755
788
|
return { attach, detach };
|
|
756
789
|
}
|
|
@@ -1082,7 +1115,11 @@ function getWindowWidth() {
|
|
|
1082
1115
|
return document.documentElement.clientWidth;
|
|
1083
1116
|
}
|
|
1084
1117
|
function getWindowHeight() {
|
|
1085
|
-
|
|
1118
|
+
const visualViewport = window.visualViewport;
|
|
1119
|
+
if (visualViewport) {
|
|
1120
|
+
return visualViewport.height * visualViewport.scale;
|
|
1121
|
+
}
|
|
1122
|
+
return document.documentElement.clientHeight;
|
|
1086
1123
|
}
|
|
1087
1124
|
function isElementArray(source) {
|
|
1088
1125
|
return source.length > 0 && source[0] instanceof Element;
|
|
@@ -1113,6 +1150,44 @@ var SmartPhoto = class {
|
|
|
1113
1150
|
this.timeouts = [];
|
|
1114
1151
|
this.loadAllFired = /* @__PURE__ */ new Set();
|
|
1115
1152
|
this.syncedGroupId = null;
|
|
1153
|
+
// 文字列セレクタで構築された場合のみ設定される。Ajax等で後から追加された
|
|
1154
|
+
// 要素をクリック時に動的検出するための再スキャン起点(§discoverGroupElements)
|
|
1155
|
+
this.rootSelector = null;
|
|
1156
|
+
// クリックされた要素(またはその祖先)から登録済み Item を逆引きするための
|
|
1157
|
+
// キャッシュ。documentへのイベントデリゲーションで祖先チェーンを辿る際に使う
|
|
1158
|
+
this.itemsByElement = /* @__PURE__ */ new Map();
|
|
1159
|
+
// サムネイルクリックはこの1本の document デリゲーションリスナーだけで処理する
|
|
1160
|
+
// (個別バインドは廃止)。同じクリックイベントを複数の SmartPhoto インスタンスが
|
|
1161
|
+
// 二重処理しないよう、処理済みマーカーをイベント自体に立てて後続インスタンスに
|
|
1162
|
+
// 早期returnさせる(セレクタが重複するインスタンスが同時に存在するケース)
|
|
1163
|
+
this.handleDocumentClick = (e) => {
|
|
1164
|
+
if (!(e.target instanceof Element)) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const marker = e;
|
|
1168
|
+
if (marker.__smartphotoClaimed) {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
let matched = this.findRegisteredAncestor(e.target);
|
|
1172
|
+
if (!matched && this.rootSelector) {
|
|
1173
|
+
matched = e.target.closest(this.rootSelector);
|
|
1174
|
+
}
|
|
1175
|
+
if (!matched) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
e.preventDefault();
|
|
1179
|
+
marker.__smartphotoClaimed = true;
|
|
1180
|
+
if (!this.itemsByElement.has(matched) && this.rootSelector) {
|
|
1181
|
+
const groupId = groupIdFromElement(matched);
|
|
1182
|
+
if (this.resyncGroupFromDom(groupId)) {
|
|
1183
|
+
this.syncCurrentGroupView();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
const item = this.itemsByElement.get(matched);
|
|
1187
|
+
if (item) {
|
|
1188
|
+
this.openPhoto(item, matched);
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1116
1191
|
// ---- 内部: window イベント ----
|
|
1117
1192
|
this.updateViewportHeight = () => {
|
|
1118
1193
|
this.view.refs.dialog.style.setProperty(
|
|
@@ -1124,6 +1199,7 @@ var SmartPhoto = class {
|
|
|
1124
1199
|
if (!currentItems(this.state)) {
|
|
1125
1200
|
return;
|
|
1126
1201
|
}
|
|
1202
|
+
this.updateViewportHeight();
|
|
1127
1203
|
this.resetTranslateCurrent();
|
|
1128
1204
|
this.setPosByCurrentIndex();
|
|
1129
1205
|
this.setSizeByScreen();
|
|
@@ -1146,6 +1222,7 @@ var SmartPhoto = class {
|
|
|
1146
1222
|
if (!currentItems(this.state)) {
|
|
1147
1223
|
return;
|
|
1148
1224
|
}
|
|
1225
|
+
this.updateViewportHeight();
|
|
1149
1226
|
this.resetTranslateCurrent();
|
|
1150
1227
|
this.setPosByCurrentIndex();
|
|
1151
1228
|
this.setHashByCurrentIndex();
|
|
@@ -1156,6 +1233,7 @@ var SmartPhoto = class {
|
|
|
1156
1233
|
const poll = (time) => {
|
|
1157
1234
|
this.scheduleTimeout(() => {
|
|
1158
1235
|
if (prevWidth !== getWindowWidth()) {
|
|
1236
|
+
this.updateViewportHeight();
|
|
1159
1237
|
this.resetTranslateCurrent();
|
|
1160
1238
|
this.setPosByCurrentIndex();
|
|
1161
1239
|
this.setHashByCurrentIndex();
|
|
@@ -1168,6 +1246,7 @@ var SmartPhoto = class {
|
|
|
1168
1246
|
};
|
|
1169
1247
|
poll(0);
|
|
1170
1248
|
};
|
|
1249
|
+
this.rootSelector = typeof source === "string" ? source : null;
|
|
1171
1250
|
this.state = createState(settings ?? {});
|
|
1172
1251
|
this.view = createView(
|
|
1173
1252
|
{ id: this.id, options: this.state.options },
|
|
@@ -1189,15 +1268,14 @@ var SmartPhoto = class {
|
|
|
1189
1268
|
},
|
|
1190
1269
|
{ signal: this.abortController.signal }
|
|
1191
1270
|
);
|
|
1271
|
+
document.addEventListener("click", this.handleDocumentClick, {
|
|
1272
|
+
signal: this.abortController.signal
|
|
1273
|
+
});
|
|
1192
1274
|
this.ingestSource(source);
|
|
1193
1275
|
this.syncCurrentGroupView();
|
|
1194
1276
|
const restored = this.restoreFromHash();
|
|
1195
1277
|
if (restored) {
|
|
1196
|
-
|
|
1197
|
-
triggerEvent(restored.element, "click");
|
|
1198
|
-
} else {
|
|
1199
|
-
this.openPhoto(restored, null);
|
|
1200
|
-
}
|
|
1278
|
+
this.openPhoto(restored, restored.element);
|
|
1201
1279
|
}
|
|
1202
1280
|
this.updateViewportHeight();
|
|
1203
1281
|
if (window.visualViewport) {
|
|
@@ -1242,6 +1320,7 @@ var SmartPhoto = class {
|
|
|
1242
1320
|
clearTimeout(id);
|
|
1243
1321
|
});
|
|
1244
1322
|
this.timeouts = [];
|
|
1323
|
+
this.itemsByElement.clear();
|
|
1245
1324
|
this.gestures.detach();
|
|
1246
1325
|
this.view.destroy();
|
|
1247
1326
|
}
|
|
@@ -1354,7 +1433,7 @@ var SmartPhoto = class {
|
|
|
1354
1433
|
this.gotoSlide(this.state.viewer.prev);
|
|
1355
1434
|
}
|
|
1356
1435
|
addItem(slideOrElement) {
|
|
1357
|
-
const item = slideOrElement instanceof
|
|
1436
|
+
const item = slideOrElement instanceof HTMLElement ? this.addElementItem(slideOrElement) : this.addSlideItem(slideOrElement);
|
|
1358
1437
|
this.syncCurrentGroupView();
|
|
1359
1438
|
return item;
|
|
1360
1439
|
}
|
|
@@ -1385,7 +1464,7 @@ var SmartPhoto = class {
|
|
|
1385
1464
|
);
|
|
1386
1465
|
addItemToGroup(this.state, item);
|
|
1387
1466
|
this.loadAllFired.delete(groupId);
|
|
1388
|
-
this.
|
|
1467
|
+
this.itemsByElement.set(element, item);
|
|
1389
1468
|
return item;
|
|
1390
1469
|
}
|
|
1391
1470
|
addSlideItem(slide) {
|
|
@@ -1396,15 +1475,72 @@ var SmartPhoto = class {
|
|
|
1396
1475
|
this.loadAllFired.delete(groupId);
|
|
1397
1476
|
return item;
|
|
1398
1477
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1478
|
+
// クリックされた要素(またはその祖先。<a><img></a> の img がクリックされる
|
|
1479
|
+
// ケースを含む)から、登録済み Item を持つ要素まで祖先チェーンを遡って探す
|
|
1480
|
+
findRegisteredAncestor(target) {
|
|
1481
|
+
let node = target;
|
|
1482
|
+
while (node) {
|
|
1483
|
+
if (this.itemsByElement.has(node)) {
|
|
1484
|
+
return node;
|
|
1485
|
+
}
|
|
1486
|
+
node = node.parentElement;
|
|
1487
|
+
}
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
// rootSelector(文字列セレクタで構築した場合のみ設定される)を再スキャンし、
|
|
1491
|
+
// 指定グループを現在のDOM状態に合わせて丸ごと再構築する(追加・削除・並び順の
|
|
1492
|
+
// 変化を一度に反映)。既知要素は既存の Item オブジェクトをそのまま再利用する
|
|
1493
|
+
// ため loaded/width/height は保持され、DOM から消えた要素だけ itemsByElement
|
|
1494
|
+
// からも除去する。index/translateX は resetTranslate() で再計算する。
|
|
1495
|
+
// 呼び出し元(openPhoto)がこの結果を使い、変化があった場合のみ view を
|
|
1496
|
+
// 再同期する(§ダイアログを開く瞬間に整合を取る。開いている間の next()/prev()
|
|
1497
|
+
// では呼ばれないため、この間の追加/削除は次に開き直すまで反映されない)
|
|
1498
|
+
resyncGroupFromDom(groupId) {
|
|
1499
|
+
if (!this.rootSelector) {
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
const domElements = Array.from(
|
|
1503
|
+
document.querySelectorAll(this.rootSelector)
|
|
1504
|
+
).filter((el) => groupIdFromElement(el) === groupId);
|
|
1505
|
+
const domElementSet = new Set(domElements);
|
|
1506
|
+
const previous = this.state.groups.get(groupId) ?? [];
|
|
1507
|
+
const previousByElement = /* @__PURE__ */ new Map();
|
|
1508
|
+
previous.forEach((it) => {
|
|
1509
|
+
if (it.element) {
|
|
1510
|
+
previousByElement.set(it.element, it);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
let changed = domElements.length !== previous.length;
|
|
1514
|
+
const rebuilt = domElements.map((el, index) => {
|
|
1515
|
+
const existing = previousByElement.get(el);
|
|
1516
|
+
if (existing) {
|
|
1517
|
+
if (existing.index !== index) {
|
|
1518
|
+
changed = true;
|
|
1519
|
+
existing.index = index;
|
|
1520
|
+
}
|
|
1521
|
+
return existing;
|
|
1522
|
+
}
|
|
1523
|
+
changed = true;
|
|
1524
|
+
const item = itemFromElement(
|
|
1525
|
+
el,
|
|
1526
|
+
this.state.options,
|
|
1527
|
+
index,
|
|
1528
|
+
getWindowWidth()
|
|
1529
|
+
);
|
|
1530
|
+
this.itemsByElement.set(el, item);
|
|
1531
|
+
return item;
|
|
1532
|
+
});
|
|
1533
|
+
previous.forEach((it) => {
|
|
1534
|
+
if (it.element && !domElementSet.has(it.element)) {
|
|
1535
|
+
this.itemsByElement.delete(it.element);
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
resetTranslate(rebuilt, getWindowWidth());
|
|
1539
|
+
this.state.groups.set(groupId, rebuilt);
|
|
1540
|
+
if (changed) {
|
|
1541
|
+
this.loadAllFired.delete(groupId);
|
|
1542
|
+
}
|
|
1543
|
+
return changed;
|
|
1408
1544
|
}
|
|
1409
1545
|
syncCurrentGroupView() {
|
|
1410
1546
|
const items = currentItems(this.state);
|
|
@@ -1658,10 +1794,11 @@ var SmartPhoto = class {
|
|
|
1658
1794
|
});
|
|
1659
1795
|
}
|
|
1660
1796
|
openPhoto(item, trigger) {
|
|
1797
|
+
const groupChanged = this.rootSelector ? this.resyncGroupFromDom(item.groupId) : false;
|
|
1661
1798
|
this.lastTriggerElement = trigger;
|
|
1662
1799
|
this.state.viewer.currentGroup = item.groupId;
|
|
1663
1800
|
this.state.viewer.currentIndex = item.index;
|
|
1664
|
-
if (this.syncedGroupId !== item.groupId) {
|
|
1801
|
+
if (this.syncedGroupId !== item.groupId || groupChanged) {
|
|
1665
1802
|
this.syncCurrentGroupView();
|
|
1666
1803
|
}
|
|
1667
1804
|
this.setHashByCurrentIndex();
|
|
@@ -1752,6 +1889,7 @@ var SmartPhoto = class {
|
|
|
1752
1889
|
this.state.viewer.photoPosX = 0;
|
|
1753
1890
|
this.state.viewer.photoPosY = 0;
|
|
1754
1891
|
this.state.viewer.onMove = true;
|
|
1892
|
+
this.updateViewportHeight();
|
|
1755
1893
|
this.setPosByCurrentIndex();
|
|
1756
1894
|
this.setHashByCurrentIndex();
|
|
1757
1895
|
this.setSizeByScreen();
|