Skip to content

Commit a5b8ea4

Browse files
committed
✨: refactor autoScaleIOS.js to improve canvas capture and scaling functionality
1 parent d413c0f commit a5b8ea4

File tree

2 files changed

+219
-61
lines changed

2 files changed

+219
-61
lines changed

assets/js/autoScaleIOS.js

Lines changed: 183 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,185 @@
1-
// try {
2-
// console.log("game_frame and its contentDocument found.");
3-
// var innerNode = document.getElementById('$styleIdI');
4-
// if (innerNode) document.getElementsByTagName('head')[0].removeChild(innerNode);
5-
// innerNode = document.createElement('div');
6-
7-
// var shownode = document.getElementById('flashWrap');
8-
// console.log("flashWrap: " + shownode);
9-
10-
// innerNode.innerHTML = `
11-
// <style id="$styleIdI">
12-
// body { visibility: hidden; }
13-
// #flashWrap { position: fixed; left: 0; top: 0; width: 100% !important; height: 100% !important; }
14-
// #htmlWrap { visibility: visible; width: 100% !important; height: 100% !important; }
15-
// // body > *:not(#flashWrap):not(#htmlWrap) { display: none; }
16-
// </style>`;
17-
18-
// document.getElementsByTagName('head')[0].appendChild(innerNode.lastChild);
19-
// console.log("Inner CSS injected successfully.");
20-
// } catch(e) {
21-
// alert("ページCSS適用に失敗しました: " + e);
22-
// }
23-
24-
(($, _) => {
25-
const html =$.documentElement,
26-
gf = $.getElementById('game_frame');
1+
/**
2+
* Auto scale script for KanColle DMM page (iOS/Flutter webview friendly).
3+
*
4+
* Goals:
5+
* - Hide surrounding DMM chrome/ads and only show the game surface.
6+
* - Keep the 1200x720 game area centered and scaled to fit the viewport.
7+
* - Be idempotent: re‑running only re-applies sizing, does not stack listeners.
8+
*/
9+
(function autoScaleKancolle() {
10+
const STYLE_ID = '__kc_auto_scale_css__';
11+
const FLAG_KEY = '__kc_auto_scale_bound__';
12+
const GAME_W = 1200;
13+
const GAME_H = 720;
14+
let lastScale = null;
15+
let lastVW = 0;
16+
let lastVH = 0;
17+
18+
// If already bound, just trigger a forced refresh and exit.
19+
if (window[FLAG_KEY]) {
20+
if (typeof window.__kcAutoScaleRefresh === 'function') {
21+
window.__kcAutoScaleRefresh({ reset: true });
22+
}
23+
return;
24+
}
25+
window[FLAG_KEY] = true;
26+
27+
const hideSelectors = [
28+
'#header',
29+
'#ntg-recommend',
30+
'#dmm-ntgnavi',
31+
'.dmm-ntgnavi',
32+
'.area-naviapp',
33+
'.area-common',
34+
'header',
35+
'footer',
36+
'#foot',
37+
'#footer',
38+
'.area-footer',
39+
'.d-footer',
40+
'.social-wrap',
41+
'.gameStart',
42+
'iframe[title*="ad"]',
43+
'iframe[src*="ads"]',
44+
];
45+
46+
function injectStyle(doc, cssText) {
47+
if (!doc || doc.getElementById(STYLE_ID)) return;
48+
const style = doc.createElement('style');
49+
style.id = STYLE_ID;
50+
style.textContent = cssText;
51+
(doc.head || doc.documentElement).appendChild(style);
52+
}
53+
54+
function buildBaseCSS() {
55+
return `
56+
html, body {
57+
margin: 0 !important;
58+
padding: 0 !important;
59+
overflow: hidden !important;
60+
background: #000 !important;
61+
width: 100% !important;
62+
height: 100% !important;
63+
}
64+
${hideSelectors.join(',')} { display: none !important; }
65+
#main-ntg, #w, #contents, #container, #root {
66+
margin: 0 !important;
67+
padding: 0 !important;
68+
width: 100% !important;
69+
height: 100% !important;
70+
}
71+
`;
72+
}
73+
74+
function findGameFrame() {
75+
return (
76+
document.getElementById('game_frame') ||
77+
document.querySelector('iframe[src*="kancolle"]') ||
78+
document.querySelector('iframe[src*="kcs"]') ||
79+
document.querySelector('iframe[src*="gadgets"]')
80+
);
81+
}
82+
83+
function resetFrameStyle(gf) {
84+
const style = gf.style;
85+
style.position = 'fixed';
86+
style.width = `${GAME_W}px`;
87+
style.height = `${GAME_H}px`;
88+
style.transform = 'none';
89+
style.transformOrigin = 'top left';
90+
style.border = '0';
91+
style.margin = '0';
92+
style.padding = '0';
93+
style.left = '0';
94+
style.top = '0';
95+
style.zIndex = '9999';
96+
}
97+
98+
function applyScale({ reset = false } = {}) {
99+
const gf = findGameFrame();
100+
injectStyle(document, buildBaseCSS());
27101
if (!gf) return;
28-
const gs = gf.style,
29-
gw = gf.offsetWidth,
30-
gh = gw * .6;
31-
let vp = $.querySelector('meta[name=viewport]'),
32-
t = 0;
33-
vp || (vp = $.createElement('meta'), vp.name = 'viewport', $.querySelector('head').appendChild(vp));
34-
vp.content = 'width=' + gw;
35-
// 'orientation' in _ && html.webkitRequestFullscreen && html.webkitRequestFullscreen();
36-
html.style.overflow = 'hidden';$.body.style.cssText = 'min-width:0;padding:0;margin:0;overflow:hidden;margin:0';$.querySelector('.dmm-ntgnavi').style.display = 'none';$.querySelector('.area-naviapp').style.display = 'none';
37-
gs.position = 'fixed';
38-
gs.marginRight = 'auto';
39-
gs.marginLeft = 'auto';
40-
gs.top = '-16px';
41-
gs.right = '0';
42-
gs.zIndex = '100';
43-
gs.transformOrigin = 'center top';
44-
if (!_.kancolleFit) {
45-
const k = () => {
46-
const w = html.clientWidth,
47-
h = _.innerHeight;
48-
w / h < 1 / .6 ? gs.transform = 'scale(' + w / gw + ')' : gs.transform = 'scale(' + h / gh + ')';
49-
w < gw ? gs.left = '-' + (gw - w) / 2 + 'px' : gs.left = '0'
50-
};
51-
_.addEventListener('resize', () => {
52-
clearTimeout(t);
53-
t = setTimeout(k, 10)
54-
});
55-
_.kancolleFit = k
102+
103+
if (reset) {
104+
resetFrameStyle(gf);
105+
lastScale = null;
106+
lastVW = 0;
107+
lastVH = 0;
108+
}
109+
110+
// Compute scale to fit viewport.
111+
const vw = window.innerWidth;
112+
const vh = window.innerHeight;
113+
114+
// Only accept real size changes (user resize / orientation), ignore hover-induced jitters.
115+
if (
116+
!reset &&
117+
Math.abs(vw - lastVW) < 8 && // <8px delta: ignore
118+
Math.abs(vh - lastVH) < 8
119+
) {
120+
return;
121+
}
122+
lastVW = vw;
123+
lastVH = vh;
124+
125+
let scale = Math.min(vw / GAME_W, vh / GAME_H);
126+
lastScale = scale;
127+
128+
const offsetX = (vw - GAME_W * scale) / 2;
129+
const offsetY = (vh - GAME_H * scale) / 2;
130+
131+
const style = gf.style;
132+
style.transformOrigin = 'top left';
133+
style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
134+
gf.dataset.kcScale = String(scale);
135+
136+
// Keep parent from adding padding.
137+
const parent = gf.parentElement;
138+
if (parent) {
139+
parent.style.margin = '0';
140+
parent.style.padding = '0';
141+
parent.style.width = '100%';
142+
parent.style.height = '100%';
143+
parent.style.background = '#000';
56144
}
57-
kancolleFit()
58-
})(document, window)
145+
146+
// Best-effort: if same-origin, hide inner clutter too.
147+
try {
148+
const inner = gf.contentDocument;
149+
if (inner) {
150+
injectStyle(
151+
inner,
152+
`
153+
html, body { margin: 0 !important; padding: 0 !important; overflow: hidden !important; background: #000 !important; }
154+
#htmlWrap, #flashWrap { width: ${GAME_W}px !important; height: ${GAME_H}px !important; }
155+
body > *:not(#htmlWrap):not(#flashWrap) { display: none !important; }
156+
`,
157+
);
158+
}
159+
} catch (e) {
160+
// Cross-origin; ignore.
161+
}
162+
}
163+
164+
// Public refresh hook for subsequent triggers.
165+
window.__kcAutoScaleRefresh = (opts = {}) =>
166+
requestAnimationFrame(() => applyScale(opts));
167+
168+
// Run once DOM is ready.
169+
if (document.readyState === 'loading') {
170+
document.addEventListener(
171+
'DOMContentLoaded',
172+
() => window.__kcAutoScaleRefresh({ reset: true }),
173+
{ once: true },
174+
);
175+
} else {
176+
window.__kcAutoScaleRefresh({ reset: true });
177+
}
178+
179+
// Avoid auto-resize on minor UI chrome changes; users can tap the scale button to refresh.
180+
window.addEventListener(
181+
'orientationchange',
182+
() => window.__kcAutoScaleRefresh({ reset: true }),
183+
{ passive: true },
184+
);
185+
})();

lib/components/webview.dart

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:squadron_en_route/components/kancolle_model/kancolle_parse.dart'
77
import 'package:squadron_en_route/constant.dart';
88
import 'package:squadron_en_route/helper/mod.dart';
99
import 'package:uuid/uuid.dart';
10+
import 'dart:io';
1011
import '../main.dart';
1112

1213
class AppWebView extends StatefulWidget {
@@ -79,11 +80,28 @@ class AppWebViewState extends State<AppWebView> {
7980
print('Page started loading: $uri');
8081
},
8182
onLoadStop: (controller, uri) async {},
83+
onReceivedServerTrustAuthRequest: (controller, request) async {
84+
// 仅对受信任的主机放行自签名证书,避免全局信任带来安全风险。
85+
final reqStr = request.toString();
86+
final allowedHost = AppConstants.kancolleUri.host;
87+
// 放行条件:请求描述中包含配置的 host,或包含关键字 'kancolle'
88+
var allow = reqStr.contains(allowedHost) || reqStr.contains('kancolle');
89+
// 如果在 macOS 上运行,作为开发临时兼容,允许通过(注意安全风险)
90+
if (!allow && Platform.isMacOS) {
91+
allow = true;
92+
}
93+
print('ServerTrustAuthRequest: runtimeType=${request.runtimeType}, request=$reqStr, allow=$allow');
94+
return ServerTrustAuthResponse(
95+
action: allow
96+
? ServerTrustAuthResponseAction.PROCEED
97+
: ServerTrustAuthResponseAction.CANCEL);
98+
},
8299
onReceivedError: (controller, request, error) {
83-
if (error.type == -1007) {
84-
// Handle the error here
85-
print('Error: ${error.description}');
86-
// You can show an alert dialog or a custom error page
100+
// 打印错误代码与描述,避免直接与枚举或 int 比较导致类型错误
101+
try {
102+
print('WebView error - $error');
103+
} catch (e) {
104+
print('onReceivedError: $e');
87105
}
88106
},
89107
onConsoleMessage: (controller, consoleMessage) =>
@@ -141,7 +159,20 @@ class AppWebViewState extends State<AppWebView> {
141159
),
142160
wrappedItem: CommandBarButton(
143161
icon: const Icon(FluentIcons.back_to_window),
144-
onPressed: () {
162+
onPressed: () async {
163+
try {
164+
final hasRefresh = await controller.evaluateJavascript(
165+
source:
166+
"typeof window.__kcAutoScaleRefresh === 'function'");
167+
if (hasRefresh == true || hasRefresh == 'true') {
168+
await controller.evaluateJavascript(
169+
source:
170+
"window.__kcAutoScaleRefresh({ reset: true });");
171+
return;
172+
}
173+
} catch (_) {
174+
// fallthrough to inject if eval fails
175+
}
145176
controller.injectJavascriptFileFromAsset(
146177
assetFilePath: 'assets/js/autoScaleIOS.js');
147178
},

0 commit comments

Comments
 (0)