@adstage/web-sdk 2.1.2 → 2.1.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/dist/index.cjs.js +362 -21
- package/dist/index.d.ts +40 -0
- package/dist/index.esm.js +362 -21
- package/dist/index.standalone.js +362 -21
- package/package.json +1 -1
- package/src/modules/ads/AdsModule.ts +425 -21
package/dist/index.cjs.js
CHANGED
|
@@ -2221,8 +2221,10 @@ class AdsModule {
|
|
|
2221
2221
|
adElement.setAttribute('data-adstage-container', 'true');
|
|
2222
2222
|
adElement.setAttribute('data-adstage-type', type);
|
|
2223
2223
|
adElement.setAttribute('data-adstage-slot', slotId);
|
|
2224
|
-
|
|
2225
|
-
|
|
2224
|
+
// 스마트한 크기 설정
|
|
2225
|
+
const { width, height } = this.calculateAdSize(container, type, options);
|
|
2226
|
+
adElement.style.width = width;
|
|
2227
|
+
adElement.style.height = height;
|
|
2226
2228
|
adElement.style.border = '1px dashed #ccc';
|
|
2227
2229
|
adElement.style.display = 'flex';
|
|
2228
2230
|
adElement.style.alignItems = 'center';
|
|
@@ -2232,7 +2234,266 @@ class AdsModule {
|
|
|
2232
2234
|
adElement.innerHTML = `<span>Loading ${type} ad...</span>`;
|
|
2233
2235
|
container.appendChild(adElement);
|
|
2234
2236
|
if (this._config?.debug) {
|
|
2235
|
-
console.log(`📦 Placeholder created for slot: ${slotId}`);
|
|
2237
|
+
console.log(`📦 Placeholder created for slot: ${slotId} (${width} x ${height})`);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* 컨테이너와 광고 타입에 따른 스마트한 크기 계산
|
|
2242
|
+
*/
|
|
2243
|
+
calculateAdSize(container, type, options) {
|
|
2244
|
+
// 사용자가 명시적으로 크기를 지정한 경우
|
|
2245
|
+
const explicitWidth = options.width;
|
|
2246
|
+
const explicitHeight = options.height;
|
|
2247
|
+
// 너비 처리
|
|
2248
|
+
let width;
|
|
2249
|
+
if (typeof explicitWidth === 'number') {
|
|
2250
|
+
width = `${explicitWidth}px`;
|
|
2251
|
+
}
|
|
2252
|
+
else if (typeof explicitWidth === 'string') {
|
|
2253
|
+
width = explicitWidth;
|
|
2254
|
+
}
|
|
2255
|
+
else {
|
|
2256
|
+
width = '100%'; // 기본값은 100%
|
|
2257
|
+
}
|
|
2258
|
+
// 높이 처리 - 핵심 로직
|
|
2259
|
+
let height;
|
|
2260
|
+
if (typeof explicitHeight === 'number') {
|
|
2261
|
+
height = `${explicitHeight}px`;
|
|
2262
|
+
}
|
|
2263
|
+
else if (typeof explicitHeight === 'string' && explicitHeight !== '100%') {
|
|
2264
|
+
height = explicitHeight;
|
|
2265
|
+
}
|
|
2266
|
+
else {
|
|
2267
|
+
// 100%이거나 높이가 지정되지 않은 경우 스마트 계산
|
|
2268
|
+
const containerHeight = this.getContainerHeight(container);
|
|
2269
|
+
if (containerHeight > 0) {
|
|
2270
|
+
// 컨테이너에 높이가 있으면 100% 사용
|
|
2271
|
+
height = '100%';
|
|
2272
|
+
if (this._config?.debug) {
|
|
2273
|
+
console.log(`📏 Using 100% height (container: ${containerHeight}px)`);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
// 컨테이너에 높이가 없으면 타입별 기본값 사용
|
|
2278
|
+
height = this.getDefaultHeightForAdType(type);
|
|
2279
|
+
if (this._config?.debug) {
|
|
2280
|
+
console.log(`📏 Using default height ${height} (no container height)`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return { width, height };
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* 컨테이너의 실제 높이 계산
|
|
2288
|
+
*/
|
|
2289
|
+
getContainerHeight(container) {
|
|
2290
|
+
// 현재 계산된 스타일에서 높이 확인
|
|
2291
|
+
const computedStyle = window.getComputedStyle(container);
|
|
2292
|
+
const height = parseFloat(computedStyle.height);
|
|
2293
|
+
// height가 auto이거나 0이면 다른 방법들 시도
|
|
2294
|
+
if (!height || height === 0) {
|
|
2295
|
+
// min-height 확인
|
|
2296
|
+
const minHeight = parseFloat(computedStyle.minHeight);
|
|
2297
|
+
if (minHeight > 0)
|
|
2298
|
+
return minHeight;
|
|
2299
|
+
// CSS로 설정된 고정 높이 확인
|
|
2300
|
+
if (container.style.height && container.style.height !== 'auto') {
|
|
2301
|
+
const styleHeight = parseFloat(container.style.height);
|
|
2302
|
+
if (styleHeight > 0)
|
|
2303
|
+
return styleHeight;
|
|
2304
|
+
}
|
|
2305
|
+
// 속성으로 설정된 높이 확인
|
|
2306
|
+
const heightAttr = container.getAttribute('height');
|
|
2307
|
+
if (heightAttr) {
|
|
2308
|
+
const attrHeight = parseFloat(heightAttr);
|
|
2309
|
+
if (attrHeight > 0)
|
|
2310
|
+
return attrHeight;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
return height || 0;
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* 광고 타입별 기본 높이 반환
|
|
2317
|
+
*/
|
|
2318
|
+
getDefaultHeightForAdType(type) {
|
|
2319
|
+
switch (type) {
|
|
2320
|
+
case AdType.BANNER:
|
|
2321
|
+
return '250px'; // 일반 배너
|
|
2322
|
+
case AdType.TEXT:
|
|
2323
|
+
return '120px'; // 텍스트는 좀 더 작게
|
|
2324
|
+
case AdType.VIDEO:
|
|
2325
|
+
return '360px'; // 비디오는 16:9 비율 고려
|
|
2326
|
+
case AdType.NATIVE:
|
|
2327
|
+
return '200px'; // 네이티브는 중간 크기
|
|
2328
|
+
case AdType.INTERSTITIAL:
|
|
2329
|
+
return '400px'; // 전면광고는 크게
|
|
2330
|
+
default:
|
|
2331
|
+
return '250px';
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* 이미지 크기 정보 로드 (프리로딩)
|
|
2336
|
+
*/
|
|
2337
|
+
async loadImageDimensions(imageUrl) {
|
|
2338
|
+
return new Promise((resolve, reject) => {
|
|
2339
|
+
const img = new Image();
|
|
2340
|
+
img.onload = () => {
|
|
2341
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
2342
|
+
};
|
|
2343
|
+
img.onerror = () => {
|
|
2344
|
+
reject(new Error(`Failed to load image: ${imageUrl}`));
|
|
2345
|
+
};
|
|
2346
|
+
img.src = imageUrl;
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* 여러 광고의 최적 컨테이너 크기 계산 (동적 크기 조정)
|
|
2351
|
+
*/
|
|
2352
|
+
async calculateOptimalContainerSize(advertisements, containerWidth, adType) {
|
|
2353
|
+
if (!advertisements.length || adType !== AdType.BANNER) {
|
|
2354
|
+
// 배너가 아니거나 광고가 없으면 기본값
|
|
2355
|
+
return {
|
|
2356
|
+
width: '100%',
|
|
2357
|
+
height: this.getDefaultHeightForAdType(adType),
|
|
2358
|
+
aspectRatio: 16 / 9
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
try {
|
|
2362
|
+
// 모든 배너 이미지의 크기 정보 로드
|
|
2363
|
+
const imageDimensions = await Promise.allSettled(advertisements
|
|
2364
|
+
.filter(ad => ad.imageUrl)
|
|
2365
|
+
.map(ad => this.loadImageDimensions(ad.imageUrl)));
|
|
2366
|
+
const validDimensions = imageDimensions
|
|
2367
|
+
.filter((result) => result.status === 'fulfilled')
|
|
2368
|
+
.map(result => result.value);
|
|
2369
|
+
if (validDimensions.length === 0) {
|
|
2370
|
+
// 이미지 로드 실패시 기본값
|
|
2371
|
+
return {
|
|
2372
|
+
width: '100%',
|
|
2373
|
+
height: this.getDefaultHeightForAdType(adType),
|
|
2374
|
+
aspectRatio: 16 / 9
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
// 최적 전략 선택
|
|
2378
|
+
const strategy = this.selectOptimalSizeStrategy(validDimensions);
|
|
2379
|
+
const optimalHeight = this.calculateOptimalHeight(validDimensions, containerWidth, strategy);
|
|
2380
|
+
if (this._config?.debug) {
|
|
2381
|
+
console.log(`📐 Optimal container calculated: ${containerWidth}x${optimalHeight} (strategy: ${strategy})`);
|
|
2382
|
+
}
|
|
2383
|
+
return {
|
|
2384
|
+
width: '100%',
|
|
2385
|
+
height: `${optimalHeight}px`,
|
|
2386
|
+
aspectRatio: containerWidth / optimalHeight
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
catch (error) {
|
|
2390
|
+
console.warn('Failed to calculate optimal size, using defaults:', error);
|
|
2391
|
+
return {
|
|
2392
|
+
width: '100%',
|
|
2393
|
+
height: this.getDefaultHeightForAdType(adType),
|
|
2394
|
+
aspectRatio: 16 / 9
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* 최적 크기 조정 전략 선택
|
|
2400
|
+
*/
|
|
2401
|
+
selectOptimalSizeStrategy(dimensions) {
|
|
2402
|
+
const aspectRatios = dimensions.map(d => d.width / d.height);
|
|
2403
|
+
// 1. 공통 비율이 있는지 확인 (±0.1 허용)
|
|
2404
|
+
const ratioGroups = new Map();
|
|
2405
|
+
aspectRatios.forEach(ratio => {
|
|
2406
|
+
const roundedRatio = Math.round(ratio * 10) / 10;
|
|
2407
|
+
const key = roundedRatio.toString();
|
|
2408
|
+
ratioGroups.set(key, (ratioGroups.get(key) || 0) + 1);
|
|
2409
|
+
});
|
|
2410
|
+
const maxGroup = Math.max(...ratioGroups.values());
|
|
2411
|
+
const totalImages = dimensions.length;
|
|
2412
|
+
// 70% 이상이 비슷한 비율이면 dominant 전략
|
|
2413
|
+
if (maxGroup / totalImages >= 0.7) {
|
|
2414
|
+
return 'dominant';
|
|
2415
|
+
}
|
|
2416
|
+
// 표준 비율들이 많으면 common 전략
|
|
2417
|
+
const standardRatios = [16 / 9, 4 / 3, 1 / 1, 3 / 2];
|
|
2418
|
+
const standardCount = aspectRatios.filter(ratio => standardRatios.some(standard => Math.abs(ratio - standard) < 0.1)).length;
|
|
2419
|
+
if (standardCount / totalImages >= 0.5) {
|
|
2420
|
+
return 'common';
|
|
2421
|
+
}
|
|
2422
|
+
// 기본은 평균 전략
|
|
2423
|
+
return 'average';
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* 전략에 따른 최적 높이 계산
|
|
2427
|
+
*/
|
|
2428
|
+
calculateOptimalHeight(dimensions, containerWidth, strategy) {
|
|
2429
|
+
const aspectRatios = dimensions.map(d => d.width / d.height);
|
|
2430
|
+
switch (strategy) {
|
|
2431
|
+
case 'dominant':
|
|
2432
|
+
// 가장 많은 비율을 기준으로
|
|
2433
|
+
const ratioGroups = new Map();
|
|
2434
|
+
aspectRatios.forEach(ratio => {
|
|
2435
|
+
const roundedRatio = Math.round(ratio * 10) / 10;
|
|
2436
|
+
const key = roundedRatio.toString();
|
|
2437
|
+
const existing = ratioGroups.get(key);
|
|
2438
|
+
if (existing) {
|
|
2439
|
+
existing.count++;
|
|
2440
|
+
}
|
|
2441
|
+
else {
|
|
2442
|
+
ratioGroups.set(key, { ratio: roundedRatio, count: 1 });
|
|
2443
|
+
}
|
|
2444
|
+
});
|
|
2445
|
+
const dominantGroup = Array.from(ratioGroups.values())
|
|
2446
|
+
.reduce((max, current) => current.count > max.count ? current : max);
|
|
2447
|
+
return Math.round(containerWidth / dominantGroup.ratio);
|
|
2448
|
+
case 'common':
|
|
2449
|
+
// 표준 비율 중 가장 적합한 것 선택
|
|
2450
|
+
const standardRatios = [
|
|
2451
|
+
{ ratio: 16 / 9, name: '16:9' },
|
|
2452
|
+
{ ratio: 4 / 3, name: '4:3' },
|
|
2453
|
+
{ ratio: 1 / 1, name: '1:1' },
|
|
2454
|
+
{ ratio: 3 / 2, name: '3:2' }
|
|
2455
|
+
];
|
|
2456
|
+
const avgRatio = aspectRatios.reduce((sum, ratio) => sum + ratio, 0) / aspectRatios.length;
|
|
2457
|
+
const bestStandard = standardRatios.reduce((best, current) => Math.abs(current.ratio - avgRatio) < Math.abs(best.ratio - avgRatio) ? current : best);
|
|
2458
|
+
if (this._config?.debug) {
|
|
2459
|
+
console.log(`📊 Using standard ratio: ${bestStandard.name} (avg: ${avgRatio.toFixed(2)})`);
|
|
2460
|
+
}
|
|
2461
|
+
return Math.round(containerWidth / bestStandard.ratio);
|
|
2462
|
+
case 'average':
|
|
2463
|
+
default:
|
|
2464
|
+
// 평균 비율 사용
|
|
2465
|
+
const averageRatio = aspectRatios.reduce((sum, ratio) => sum + ratio, 0) / aspectRatios.length;
|
|
2466
|
+
return Math.round(containerWidth / averageRatio);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* 개별 이미지에 최적화된 렌더링 스타일 적용
|
|
2471
|
+
*/
|
|
2472
|
+
applyOptimizedImageStyle(img, imageAspectRatio, containerAspectRatio) {
|
|
2473
|
+
const ratio = imageAspectRatio / containerAspectRatio;
|
|
2474
|
+
if (Math.abs(ratio - 1) < 0.1) {
|
|
2475
|
+
// 비율이 거의 같으면 cover 사용
|
|
2476
|
+
img.style.objectFit = 'cover';
|
|
2477
|
+
img.style.objectPosition = 'center';
|
|
2478
|
+
}
|
|
2479
|
+
else if (ratio > 1.3) {
|
|
2480
|
+
// 이미지가 훨씬 가로형이면 contain으로 전체 보이기
|
|
2481
|
+
img.style.objectFit = 'contain';
|
|
2482
|
+
img.style.objectPosition = 'center';
|
|
2483
|
+
img.style.backgroundColor = '#f0f0f0'; // 빈 공간 배경색
|
|
2484
|
+
}
|
|
2485
|
+
else if (ratio < 0.7) {
|
|
2486
|
+
// 이미지가 훨씬 세로형이면 cover로 채우기
|
|
2487
|
+
img.style.objectFit = 'cover';
|
|
2488
|
+
img.style.objectPosition = 'center';
|
|
2489
|
+
}
|
|
2490
|
+
else {
|
|
2491
|
+
// 적당한 차이면 스마트 cover
|
|
2492
|
+
img.style.objectFit = 'cover';
|
|
2493
|
+
img.style.objectPosition = 'center';
|
|
2494
|
+
}
|
|
2495
|
+
if (this._config?.debug) {
|
|
2496
|
+
console.log(`🎨 Image style applied: objectFit=${img.style.objectFit}, ratio=${ratio.toFixed(2)}`);
|
|
2236
2497
|
}
|
|
2237
2498
|
}
|
|
2238
2499
|
/**
|
|
@@ -2246,6 +2507,10 @@ class AdsModule {
|
|
|
2246
2507
|
this.renderFallback(slot);
|
|
2247
2508
|
return;
|
|
2248
2509
|
}
|
|
2510
|
+
// 🆕 동적 크기 조정: 배너 광고의 경우 이미지 크기 기반으로 컨테이너 최적화
|
|
2511
|
+
if (slot.adType === AdType.BANNER && adstageData.length > 0) {
|
|
2512
|
+
await this.optimizeContainerForBannerAds(slot, adstageData);
|
|
2513
|
+
}
|
|
2249
2514
|
// 광고가 여러 개이거나 autoSlide 옵션이 있으면 슬라이더로 렌더링
|
|
2250
2515
|
if (adstageData.length > 1 || slot.config?.autoSlide) {
|
|
2251
2516
|
await this.renderAdSlider(slot, adstageData);
|
|
@@ -2267,6 +2532,32 @@ class AdsModule {
|
|
|
2267
2532
|
this.renderFallback(slot);
|
|
2268
2533
|
}
|
|
2269
2534
|
}
|
|
2535
|
+
/**
|
|
2536
|
+
* 배너 광고를 위한 컨테이너 최적화
|
|
2537
|
+
*/
|
|
2538
|
+
async optimizeContainerForBannerAds(slot, advertisements) {
|
|
2539
|
+
try {
|
|
2540
|
+
const container = document.getElementById(slot.containerId);
|
|
2541
|
+
const adElement = document.getElementById(slot.id);
|
|
2542
|
+
if (!container || !adElement)
|
|
2543
|
+
return;
|
|
2544
|
+
// 현재 컨테이너 너비 확인
|
|
2545
|
+
const containerWidth = container.getBoundingClientRect().width || 300;
|
|
2546
|
+
// 최적 크기 계산
|
|
2547
|
+
const optimalSize = await this.calculateOptimalContainerSize(advertisements, containerWidth, slot.adType);
|
|
2548
|
+
// 컨테이너 크기 동적 조정
|
|
2549
|
+
adElement.style.height = optimalSize.height;
|
|
2550
|
+
// 슬롯 정보 업데이트
|
|
2551
|
+
slot.optimizedHeight = optimalSize.height;
|
|
2552
|
+
slot.aspectRatio = optimalSize.aspectRatio;
|
|
2553
|
+
if (this._config?.debug) {
|
|
2554
|
+
console.log(`🔧 Container optimized for ${advertisements.length} banner ads: ${optimalSize.height}`);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (error) {
|
|
2558
|
+
console.warn('Container optimization failed, using default size:', error);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2270
2561
|
/**
|
|
2271
2562
|
* 기본 viewability 추적 시작
|
|
2272
2563
|
*/
|
|
@@ -2489,23 +2780,25 @@ class AdsModule {
|
|
|
2489
2780
|
}
|
|
2490
2781
|
};
|
|
2491
2782
|
let sliderElement;
|
|
2783
|
+
// 최적화된 슬라이더 옵션 준비
|
|
2784
|
+
const optimizedSliderOptions = {
|
|
2785
|
+
autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
|
|
2786
|
+
...slot.config,
|
|
2787
|
+
// 🆕 동적 크기 정보 전달
|
|
2788
|
+
optimizedHeight: slot.optimizedHeight,
|
|
2789
|
+
aspectRatio: slot.aspectRatio
|
|
2790
|
+
};
|
|
2492
2791
|
// 텍스트 광고는 TextTransitionManager 사용, 그 외는 CarouselSliderManager 사용
|
|
2493
2792
|
if (slot.adType === AdType.TEXT) {
|
|
2494
|
-
sliderElement = TextTransitionManager.createTextTransitionContainer(slot, advertisements,
|
|
2495
|
-
autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
|
|
2496
|
-
...slot.config
|
|
2497
|
-
}, trackEventCallback);
|
|
2793
|
+
sliderElement = TextTransitionManager.createTextTransitionContainer(slot, advertisements, optimizedSliderOptions, trackEventCallback);
|
|
2498
2794
|
if (this._config?.debug) {
|
|
2499
2795
|
console.log(`✨ Text transition created for TEXT slot: ${slot.id} with ${advertisements.length} ads`);
|
|
2500
2796
|
}
|
|
2501
2797
|
}
|
|
2502
2798
|
else {
|
|
2503
|
-
sliderElement = CarouselSliderManager.createSliderContainer(slot, advertisements,
|
|
2504
|
-
autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
|
|
2505
|
-
...slot.config
|
|
2506
|
-
}, trackEventCallback);
|
|
2799
|
+
sliderElement = CarouselSliderManager.createSliderContainer(slot, advertisements, optimizedSliderOptions, trackEventCallback);
|
|
2507
2800
|
if (this._config?.debug) {
|
|
2508
|
-
console.log(`🎠 Carousel slider created for ${slot.adType} slot: ${slot.id} with ${advertisements.length} ads`);
|
|
2801
|
+
console.log(`🎠 Carousel slider created for ${slot.adType} slot: ${slot.id} with ${advertisements.length} ads (optimized: ${slot.optimizedHeight || 'default'})`);
|
|
2509
2802
|
}
|
|
2510
2803
|
}
|
|
2511
2804
|
// 기존 내용 제거하고 슬라이더 추가
|
|
@@ -2532,19 +2825,23 @@ class AdsModule {
|
|
|
2532
2825
|
// 기본 HTML 구조 생성
|
|
2533
2826
|
const adElement = document.createElement('div');
|
|
2534
2827
|
adElement.className = 'adstage-ad';
|
|
2535
|
-
|
|
2536
|
-
|
|
2828
|
+
// 스마트한 크기 설정 - 최적화된 크기가 있으면 사용
|
|
2829
|
+
const optimizedHeight = slot.optimizedHeight;
|
|
2830
|
+
const containerElement = container.parentElement || container;
|
|
2831
|
+
if (optimizedHeight) {
|
|
2832
|
+
adElement.style.width = '100%';
|
|
2833
|
+
adElement.style.height = optimizedHeight;
|
|
2834
|
+
}
|
|
2835
|
+
else {
|
|
2836
|
+
const { width, height } = this.calculateAdSize(containerElement, slot.adType, slot.config || {});
|
|
2837
|
+
adElement.style.width = width;
|
|
2838
|
+
adElement.style.height = height;
|
|
2839
|
+
}
|
|
2537
2840
|
// 광고 타입별 렌더링
|
|
2538
2841
|
switch (slot.adType) {
|
|
2539
2842
|
case AdType.BANNER:
|
|
2540
2843
|
if (ad.imageUrl) {
|
|
2541
|
-
|
|
2542
|
-
img.src = ad.imageUrl;
|
|
2543
|
-
img.alt = ad.title;
|
|
2544
|
-
img.style.width = '100%';
|
|
2545
|
-
img.style.height = '100%';
|
|
2546
|
-
img.style.objectFit = 'cover';
|
|
2547
|
-
adElement.appendChild(img);
|
|
2844
|
+
await this.renderOptimizedBannerImage(adElement, ad, slot);
|
|
2548
2845
|
}
|
|
2549
2846
|
break;
|
|
2550
2847
|
case AdType.TEXT:
|
|
@@ -2579,6 +2876,50 @@ class AdsModule {
|
|
|
2579
2876
|
container.innerHTML = '';
|
|
2580
2877
|
container.appendChild(adElement);
|
|
2581
2878
|
}
|
|
2879
|
+
/**
|
|
2880
|
+
* 최적화된 배너 이미지 렌더링
|
|
2881
|
+
*/
|
|
2882
|
+
async renderOptimizedBannerImage(adElement, ad, slot) {
|
|
2883
|
+
try {
|
|
2884
|
+
// 이미지 크기 정보 로드
|
|
2885
|
+
const imageDimensions = await this.loadImageDimensions(ad.imageUrl);
|
|
2886
|
+
const imageAspectRatio = imageDimensions.width / imageDimensions.height;
|
|
2887
|
+
// 컨테이너 비율 계산
|
|
2888
|
+
const containerAspectRatio = slot.aspectRatio || 16 / 9;
|
|
2889
|
+
// 이미지 요소 생성
|
|
2890
|
+
const img = document.createElement('img');
|
|
2891
|
+
img.src = ad.imageUrl;
|
|
2892
|
+
img.alt = ad.title;
|
|
2893
|
+
img.style.width = '100%';
|
|
2894
|
+
img.style.height = '100%';
|
|
2895
|
+
// 🎨 최적화된 스타일 적용
|
|
2896
|
+
this.applyOptimizedImageStyle(img, imageAspectRatio, containerAspectRatio);
|
|
2897
|
+
// 이미지 로드 완료 처리
|
|
2898
|
+
img.onload = () => {
|
|
2899
|
+
if (this._config?.debug) {
|
|
2900
|
+
console.log(`🖼️ Optimized banner image loaded: ${imageDimensions.width}x${imageDimensions.height} (ratio: ${imageAspectRatio.toFixed(2)})`);
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
// 에러 처리
|
|
2904
|
+
img.onerror = () => {
|
|
2905
|
+
console.warn(`Failed to load banner image: ${ad.imageUrl}`);
|
|
2906
|
+
// 폴백 텍스트 표시
|
|
2907
|
+
adElement.innerHTML = `<div style="display: flex; align-items: center; justify-content: center; background: #f0f0f0; color: #666;">${ad.title}</div>`;
|
|
2908
|
+
};
|
|
2909
|
+
adElement.appendChild(img);
|
|
2910
|
+
}
|
|
2911
|
+
catch (error) {
|
|
2912
|
+
console.warn('Failed to optimize banner image, using fallback:', error);
|
|
2913
|
+
// 기본 이미지 렌더링 (폴백)
|
|
2914
|
+
const img = document.createElement('img');
|
|
2915
|
+
img.src = ad.imageUrl;
|
|
2916
|
+
img.alt = ad.title;
|
|
2917
|
+
img.style.width = '100%';
|
|
2918
|
+
img.style.height = '100%';
|
|
2919
|
+
img.style.objectFit = 'cover';
|
|
2920
|
+
adElement.appendChild(img);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2582
2923
|
/**
|
|
2583
2924
|
* 광고 슬롯 새로고침
|
|
2584
2925
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -282,10 +282,46 @@ declare class AdsModule implements BaseModule {
|
|
|
282
282
|
* 즉시 광고 슬롯 생성 (placeholder)
|
|
283
283
|
*/
|
|
284
284
|
private createAdSlot;
|
|
285
|
+
/**
|
|
286
|
+
* 컨테이너와 광고 타입에 따른 스마트한 크기 계산
|
|
287
|
+
*/
|
|
288
|
+
private calculateAdSize;
|
|
289
|
+
/**
|
|
290
|
+
* 컨테이너의 실제 높이 계산
|
|
291
|
+
*/
|
|
292
|
+
private getContainerHeight;
|
|
293
|
+
/**
|
|
294
|
+
* 광고 타입별 기본 높이 반환
|
|
295
|
+
*/
|
|
296
|
+
private getDefaultHeightForAdType;
|
|
297
|
+
/**
|
|
298
|
+
* 이미지 크기 정보 로드 (프리로딩)
|
|
299
|
+
*/
|
|
300
|
+
private loadImageDimensions;
|
|
301
|
+
/**
|
|
302
|
+
* 여러 광고의 최적 컨테이너 크기 계산 (동적 크기 조정)
|
|
303
|
+
*/
|
|
304
|
+
private calculateOptimalContainerSize;
|
|
305
|
+
/**
|
|
306
|
+
* 최적 크기 조정 전략 선택
|
|
307
|
+
*/
|
|
308
|
+
private selectOptimalSizeStrategy;
|
|
309
|
+
/**
|
|
310
|
+
* 전략에 따른 최적 높이 계산
|
|
311
|
+
*/
|
|
312
|
+
private calculateOptimalHeight;
|
|
313
|
+
/**
|
|
314
|
+
* 개별 이미지에 최적화된 렌더링 스타일 적용
|
|
315
|
+
*/
|
|
316
|
+
private applyOptimizedImageStyle;
|
|
285
317
|
/**
|
|
286
318
|
* 백그라운드에서 광고 콘텐츠 로드
|
|
287
319
|
*/
|
|
288
320
|
private loadAdContentInBackground;
|
|
321
|
+
/**
|
|
322
|
+
* 배너 광고를 위한 컨테이너 최적화
|
|
323
|
+
*/
|
|
324
|
+
private optimizeContainerForBannerAds;
|
|
289
325
|
/**
|
|
290
326
|
* 기본 viewability 추적 시작
|
|
291
327
|
*/
|
|
@@ -318,6 +354,10 @@ declare class AdsModule implements BaseModule {
|
|
|
318
354
|
* 광고 요소 렌더링 (기본 구현)
|
|
319
355
|
*/
|
|
320
356
|
private renderAdElement;
|
|
357
|
+
/**
|
|
358
|
+
* 최적화된 배너 이미지 렌더링
|
|
359
|
+
*/
|
|
360
|
+
private renderOptimizedBannerImage;
|
|
321
361
|
/**
|
|
322
362
|
* 광고 슬롯 새로고침
|
|
323
363
|
*/
|