@adstage/web-sdk 2.1.2 โ†’ 2.1.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/dist/index.cjs.js CHANGED
@@ -2054,7 +2054,7 @@ class AdsModule {
2054
2054
  this.ensureReady();
2055
2055
  const adstageOptions = {
2056
2056
  width: options?.width || '100%',
2057
- height: options?.height || 250,
2057
+ height: options?.height || 'auto', // ๐Ÿ”ง ๋™์  ํฌ๊ธฐ ์กฐ์ • ํ™œ์šฉ
2058
2058
  autoSlide: options?.autoSlide || false,
2059
2059
  slideInterval: options?.slideInterval || 5000,
2060
2060
  onClick: options?.onClick,
@@ -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
- adElement.style.width = typeof options.width === 'number' ? `${options.width}px` : (options.width || '100%');
2225
- adElement.style.height = typeof options.height === 'number' ? `${options.height}px` : (options.height || '250px');
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,267 @@ 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%' && explicitHeight !== 'auto') {
2264
+ // ๋ช…์‹œ์ ์ธ ํฌ๊ธฐ ๋ฌธ์ž์—ด (์˜ˆ: '200px', '50vh' ๋“ฑ)
2265
+ height = explicitHeight;
2266
+ }
2267
+ else {
2268
+ // 100%, auto์ด๊ฑฐ๋‚˜ ๋†’์ด๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์Šค๋งˆํŠธ ๊ณ„์‚ฐ
2269
+ const containerHeight = this.getContainerHeight(container);
2270
+ if (containerHeight > 0) {
2271
+ // ์ปจํ…Œ์ด๋„ˆ์— ๋†’์ด๊ฐ€ ์žˆ์œผ๋ฉด 100% ์‚ฌ์šฉ
2272
+ height = '100%';
2273
+ if (this._config?.debug) {
2274
+ console.log(`๐Ÿ“ Using 100% height (container: ${containerHeight}px)`);
2275
+ }
2276
+ }
2277
+ else {
2278
+ // ์ปจํ…Œ์ด๋„ˆ์— ๋†’์ด๊ฐ€ ์—†์œผ๋ฉด ํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ (๋‚˜์ค‘์— ๋™์  ์กฐ์ •๋จ)
2279
+ height = this.getDefaultHeightForAdType(type);
2280
+ if (this._config?.debug) {
2281
+ console.log(`๐Ÿ“ Using default height ${height} (will be optimized for ${type})`);
2282
+ }
2283
+ }
2284
+ }
2285
+ return { width, height };
2286
+ }
2287
+ /**
2288
+ * ์ปจํ…Œ์ด๋„ˆ์˜ ์‹ค์ œ ๋†’์ด ๊ณ„์‚ฐ
2289
+ */
2290
+ getContainerHeight(container) {
2291
+ // ํ˜„์žฌ ๊ณ„์‚ฐ๋œ ์Šคํƒ€์ผ์—์„œ ๋†’์ด ํ™•์ธ
2292
+ const computedStyle = window.getComputedStyle(container);
2293
+ const height = parseFloat(computedStyle.height);
2294
+ // height๊ฐ€ auto์ด๊ฑฐ๋‚˜ 0์ด๋ฉด ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค ์‹œ๋„
2295
+ if (!height || height === 0) {
2296
+ // min-height ํ™•์ธ
2297
+ const minHeight = parseFloat(computedStyle.minHeight);
2298
+ if (minHeight > 0)
2299
+ return minHeight;
2300
+ // CSS๋กœ ์„ค์ •๋œ ๊ณ ์ • ๋†’์ด ํ™•์ธ
2301
+ if (container.style.height && container.style.height !== 'auto') {
2302
+ const styleHeight = parseFloat(container.style.height);
2303
+ if (styleHeight > 0)
2304
+ return styleHeight;
2305
+ }
2306
+ // ์†์„ฑ์œผ๋กœ ์„ค์ •๋œ ๋†’์ด ํ™•์ธ
2307
+ const heightAttr = container.getAttribute('height');
2308
+ if (heightAttr) {
2309
+ const attrHeight = parseFloat(heightAttr);
2310
+ if (attrHeight > 0)
2311
+ return attrHeight;
2312
+ }
2313
+ }
2314
+ return height || 0;
2315
+ }
2316
+ /**
2317
+ * ๊ด‘๊ณ  ํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๋†’์ด ๋ฐ˜ํ™˜
2318
+ */
2319
+ getDefaultHeightForAdType(type) {
2320
+ switch (type) {
2321
+ case AdType.BANNER:
2322
+ return '250px'; // ์ผ๋ฐ˜ ๋ฐฐ๋„ˆ
2323
+ case AdType.TEXT:
2324
+ return '120px'; // ํ…์ŠคํŠธ๋Š” ์ข€ ๋” ์ž‘๊ฒŒ
2325
+ case AdType.VIDEO:
2326
+ return '360px'; // ๋น„๋””์˜ค๋Š” 16:9 ๋น„์œจ ๊ณ ๋ ค
2327
+ case AdType.NATIVE:
2328
+ return '200px'; // ๋„ค์ดํ‹ฐ๋ธŒ๋Š” ์ค‘๊ฐ„ ํฌ๊ธฐ
2329
+ case AdType.INTERSTITIAL:
2330
+ return '400px'; // ์ „๋ฉด๊ด‘๊ณ ๋Š” ํฌ๊ฒŒ
2331
+ default:
2332
+ return '250px';
2333
+ }
2334
+ }
2335
+ /**
2336
+ * ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ •๋ณด ๋กœ๋“œ (ํ”„๋ฆฌ๋กœ๋”ฉ)
2337
+ */
2338
+ async loadImageDimensions(imageUrl) {
2339
+ return new Promise((resolve, reject) => {
2340
+ const img = new Image();
2341
+ img.onload = () => {
2342
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
2343
+ };
2344
+ img.onerror = () => {
2345
+ reject(new Error(`Failed to load image: ${imageUrl}`));
2346
+ };
2347
+ img.src = imageUrl;
2348
+ });
2349
+ }
2350
+ /**
2351
+ * ์—ฌ๋Ÿฌ ๊ด‘๊ณ ์˜ ์ตœ์  ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ๊ณ„์‚ฐ (๋™์  ํฌ๊ธฐ ์กฐ์ •)
2352
+ */
2353
+ async calculateOptimalContainerSize(advertisements, containerWidth, adType) {
2354
+ if (!advertisements.length || adType !== AdType.BANNER) {
2355
+ // ๋ฐฐ๋„ˆ๊ฐ€ ์•„๋‹ˆ๊ฑฐ๋‚˜ ๊ด‘๊ณ ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’
2356
+ return {
2357
+ width: '100%',
2358
+ height: this.getDefaultHeightForAdType(adType),
2359
+ aspectRatio: 16 / 9
2360
+ };
2361
+ }
2362
+ try {
2363
+ // ๋ชจ๋“  ๋ฐฐ๋„ˆ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ ์ •๋ณด ๋กœ๋“œ
2364
+ const imageDimensions = await Promise.allSettled(advertisements
2365
+ .filter(ad => ad.imageUrl)
2366
+ .map(ad => this.loadImageDimensions(ad.imageUrl)));
2367
+ const validDimensions = imageDimensions
2368
+ .filter((result) => result.status === 'fulfilled')
2369
+ .map(result => result.value);
2370
+ if (validDimensions.length === 0) {
2371
+ // ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ์‹œ ๊ธฐ๋ณธ๊ฐ’
2372
+ return {
2373
+ width: '100%',
2374
+ height: this.getDefaultHeightForAdType(adType),
2375
+ aspectRatio: 16 / 9
2376
+ };
2377
+ }
2378
+ // ์ตœ์  ์ „๋žต ์„ ํƒ
2379
+ const strategy = this.selectOptimalSizeStrategy(validDimensions);
2380
+ const optimalHeight = this.calculateOptimalHeight(validDimensions, containerWidth, strategy);
2381
+ if (this._config?.debug) {
2382
+ console.log(`๐Ÿ“ Optimal container calculated: ${containerWidth}x${optimalHeight} (strategy: ${strategy})`);
2383
+ }
2384
+ return {
2385
+ width: '100%',
2386
+ height: `${optimalHeight}px`,
2387
+ aspectRatio: containerWidth / optimalHeight
2388
+ };
2389
+ }
2390
+ catch (error) {
2391
+ console.warn('Failed to calculate optimal size, using defaults:', error);
2392
+ return {
2393
+ width: '100%',
2394
+ height: this.getDefaultHeightForAdType(adType),
2395
+ aspectRatio: 16 / 9
2396
+ };
2397
+ }
2398
+ }
2399
+ /**
2400
+ * ์ตœ์  ํฌ๊ธฐ ์กฐ์ • ์ „๋žต ์„ ํƒ
2401
+ */
2402
+ selectOptimalSizeStrategy(dimensions) {
2403
+ const aspectRatios = dimensions.map(d => d.width / d.height);
2404
+ // 1. ๊ณตํ†ต ๋น„์œจ์ด ์žˆ๋Š”์ง€ ํ™•์ธ (ยฑ0.1 ํ—ˆ์šฉ)
2405
+ const ratioGroups = new Map();
2406
+ aspectRatios.forEach(ratio => {
2407
+ const roundedRatio = Math.round(ratio * 10) / 10;
2408
+ const key = roundedRatio.toString();
2409
+ ratioGroups.set(key, (ratioGroups.get(key) || 0) + 1);
2410
+ });
2411
+ const maxGroup = Math.max(...ratioGroups.values());
2412
+ const totalImages = dimensions.length;
2413
+ // 70% ์ด์ƒ์ด ๋น„์Šทํ•œ ๋น„์œจ์ด๋ฉด dominant ์ „๋žต
2414
+ if (maxGroup / totalImages >= 0.7) {
2415
+ return 'dominant';
2416
+ }
2417
+ // ํ‘œ์ค€ ๋น„์œจ๋“ค์ด ๋งŽ์œผ๋ฉด common ์ „๋žต
2418
+ const standardRatios = [16 / 9, 4 / 3, 1 / 1, 3 / 2];
2419
+ const standardCount = aspectRatios.filter(ratio => standardRatios.some(standard => Math.abs(ratio - standard) < 0.1)).length;
2420
+ if (standardCount / totalImages >= 0.5) {
2421
+ return 'common';
2422
+ }
2423
+ // ๊ธฐ๋ณธ์€ ํ‰๊ท  ์ „๋žต
2424
+ return 'average';
2425
+ }
2426
+ /**
2427
+ * ์ „๋žต์— ๋”ฐ๋ฅธ ์ตœ์  ๋†’์ด ๊ณ„์‚ฐ
2428
+ */
2429
+ calculateOptimalHeight(dimensions, containerWidth, strategy) {
2430
+ const aspectRatios = dimensions.map(d => d.width / d.height);
2431
+ switch (strategy) {
2432
+ case 'dominant':
2433
+ // ๊ฐ€์žฅ ๋งŽ์€ ๋น„์œจ์„ ๊ธฐ์ค€์œผ๋กœ
2434
+ const ratioGroups = new Map();
2435
+ aspectRatios.forEach(ratio => {
2436
+ const roundedRatio = Math.round(ratio * 10) / 10;
2437
+ const key = roundedRatio.toString();
2438
+ const existing = ratioGroups.get(key);
2439
+ if (existing) {
2440
+ existing.count++;
2441
+ }
2442
+ else {
2443
+ ratioGroups.set(key, { ratio: roundedRatio, count: 1 });
2444
+ }
2445
+ });
2446
+ const dominantGroup = Array.from(ratioGroups.values())
2447
+ .reduce((max, current) => current.count > max.count ? current : max);
2448
+ return Math.round(containerWidth / dominantGroup.ratio);
2449
+ case 'common':
2450
+ // ํ‘œ์ค€ ๋น„์œจ ์ค‘ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๊ฒƒ ์„ ํƒ
2451
+ const standardRatios = [
2452
+ { ratio: 16 / 9, name: '16:9' },
2453
+ { ratio: 4 / 3, name: '4:3' },
2454
+ { ratio: 1 / 1, name: '1:1' },
2455
+ { ratio: 3 / 2, name: '3:2' }
2456
+ ];
2457
+ const avgRatio = aspectRatios.reduce((sum, ratio) => sum + ratio, 0) / aspectRatios.length;
2458
+ const bestStandard = standardRatios.reduce((best, current) => Math.abs(current.ratio - avgRatio) < Math.abs(best.ratio - avgRatio) ? current : best);
2459
+ if (this._config?.debug) {
2460
+ console.log(`๐Ÿ“Š Using standard ratio: ${bestStandard.name} (avg: ${avgRatio.toFixed(2)})`);
2461
+ }
2462
+ return Math.round(containerWidth / bestStandard.ratio);
2463
+ case 'average':
2464
+ default:
2465
+ // ํ‰๊ท  ๋น„์œจ ์‚ฌ์šฉ
2466
+ const averageRatio = aspectRatios.reduce((sum, ratio) => sum + ratio, 0) / aspectRatios.length;
2467
+ return Math.round(containerWidth / averageRatio);
2468
+ }
2469
+ }
2470
+ /**
2471
+ * ๊ฐœ๋ณ„ ์ด๋ฏธ์ง€์— ์ตœ์ ํ™”๋œ ๋ Œ๋”๋ง ์Šคํƒ€์ผ ์ ์šฉ
2472
+ */
2473
+ applyOptimizedImageStyle(img, imageAspectRatio, containerAspectRatio) {
2474
+ const ratio = imageAspectRatio / containerAspectRatio;
2475
+ if (Math.abs(ratio - 1) < 0.1) {
2476
+ // ๋น„์œจ์ด ๊ฑฐ์˜ ๊ฐ™์œผ๋ฉด cover ์‚ฌ์šฉ
2477
+ img.style.objectFit = 'cover';
2478
+ img.style.objectPosition = 'center';
2479
+ }
2480
+ else if (ratio > 1.3) {
2481
+ // ์ด๋ฏธ์ง€๊ฐ€ ํ›จ์”ฌ ๊ฐ€๋กœํ˜•์ด๋ฉด contain์œผ๋กœ ์ „์ฒด ๋ณด์ด๊ธฐ
2482
+ img.style.objectFit = 'contain';
2483
+ img.style.objectPosition = 'center';
2484
+ img.style.backgroundColor = '#f0f0f0'; // ๋นˆ ๊ณต๊ฐ„ ๋ฐฐ๊ฒฝ์ƒ‰
2485
+ }
2486
+ else if (ratio < 0.7) {
2487
+ // ์ด๋ฏธ์ง€๊ฐ€ ํ›จ์”ฌ ์„ธ๋กœํ˜•์ด๋ฉด cover๋กœ ์ฑ„์šฐ๊ธฐ
2488
+ img.style.objectFit = 'cover';
2489
+ img.style.objectPosition = 'center';
2490
+ }
2491
+ else {
2492
+ // ์ ๋‹นํ•œ ์ฐจ์ด๋ฉด ์Šค๋งˆํŠธ cover
2493
+ img.style.objectFit = 'cover';
2494
+ img.style.objectPosition = 'center';
2495
+ }
2496
+ if (this._config?.debug) {
2497
+ console.log(`๐ŸŽจ Image style applied: objectFit=${img.style.objectFit}, ratio=${ratio.toFixed(2)}`);
2236
2498
  }
2237
2499
  }
2238
2500
  /**
@@ -2246,6 +2508,10 @@ class AdsModule {
2246
2508
  this.renderFallback(slot);
2247
2509
  return;
2248
2510
  }
2511
+ // ๐Ÿ†• ๋™์  ํฌ๊ธฐ ์กฐ์ •: ๋ฐฐ๋„ˆ ๊ด‘๊ณ ์˜ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ์ตœ์ ํ™”
2512
+ if (slot.adType === AdType.BANNER && adstageData.length > 0) {
2513
+ await this.optimizeContainerForBannerAds(slot, adstageData);
2514
+ }
2249
2515
  // ๊ด‘๊ณ ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ด๊ฑฐ๋‚˜ autoSlide ์˜ต์…˜์ด ์žˆ์œผ๋ฉด ์Šฌ๋ผ์ด๋”๋กœ ๋ Œ๋”๋ง
2250
2516
  if (adstageData.length > 1 || slot.config?.autoSlide) {
2251
2517
  await this.renderAdSlider(slot, adstageData);
@@ -2267,6 +2533,32 @@ class AdsModule {
2267
2533
  this.renderFallback(slot);
2268
2534
  }
2269
2535
  }
2536
+ /**
2537
+ * ๋ฐฐ๋„ˆ ๊ด‘๊ณ ๋ฅผ ์œ„ํ•œ ์ปจํ…Œ์ด๋„ˆ ์ตœ์ ํ™”
2538
+ */
2539
+ async optimizeContainerForBannerAds(slot, advertisements) {
2540
+ try {
2541
+ const container = document.getElementById(slot.containerId);
2542
+ const adElement = document.getElementById(slot.id);
2543
+ if (!container || !adElement)
2544
+ return;
2545
+ // ํ˜„์žฌ ์ปจํ…Œ์ด๋„ˆ ๋„ˆ๋น„ ํ™•์ธ
2546
+ const containerWidth = container.getBoundingClientRect().width || 300;
2547
+ // ์ตœ์  ํฌ๊ธฐ ๊ณ„์‚ฐ
2548
+ const optimalSize = await this.calculateOptimalContainerSize(advertisements, containerWidth, slot.adType);
2549
+ // ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ๋™์  ์กฐ์ •
2550
+ adElement.style.height = optimalSize.height;
2551
+ // ์Šฌ๋กฏ ์ •๋ณด ์—…๋ฐ์ดํŠธ
2552
+ slot.optimizedHeight = optimalSize.height;
2553
+ slot.aspectRatio = optimalSize.aspectRatio;
2554
+ if (this._config?.debug) {
2555
+ console.log(`๐Ÿ”ง Container optimized for ${advertisements.length} banner ads: ${optimalSize.height}`);
2556
+ }
2557
+ }
2558
+ catch (error) {
2559
+ console.warn('Container optimization failed, using default size:', error);
2560
+ }
2561
+ }
2270
2562
  /**
2271
2563
  * ๊ธฐ๋ณธ viewability ์ถ”์  ์‹œ์ž‘
2272
2564
  */
@@ -2489,23 +2781,25 @@ class AdsModule {
2489
2781
  }
2490
2782
  };
2491
2783
  let sliderElement;
2784
+ // ์ตœ์ ํ™”๋œ ์Šฌ๋ผ์ด๋” ์˜ต์…˜ ์ค€๋น„
2785
+ const optimizedSliderOptions = {
2786
+ autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
2787
+ ...slot.config,
2788
+ // ๐Ÿ†• ๋™์  ํฌ๊ธฐ ์ •๋ณด ์ „๋‹ฌ
2789
+ optimizedHeight: slot.optimizedHeight,
2790
+ aspectRatio: slot.aspectRatio
2791
+ };
2492
2792
  // ํ…์ŠคํŠธ ๊ด‘๊ณ ๋Š” TextTransitionManager ์‚ฌ์šฉ, ๊ทธ ์™ธ๋Š” CarouselSliderManager ์‚ฌ์šฉ
2493
2793
  if (slot.adType === AdType.TEXT) {
2494
- sliderElement = TextTransitionManager.createTextTransitionContainer(slot, advertisements, {
2495
- autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
2496
- ...slot.config
2497
- }, trackEventCallback);
2794
+ sliderElement = TextTransitionManager.createTextTransitionContainer(slot, advertisements, optimizedSliderOptions, trackEventCallback);
2498
2795
  if (this._config?.debug) {
2499
2796
  console.log(`โœจ Text transition created for TEXT slot: ${slot.id} with ${advertisements.length} ads`);
2500
2797
  }
2501
2798
  }
2502
2799
  else {
2503
- sliderElement = CarouselSliderManager.createSliderContainer(slot, advertisements, {
2504
- autoSlideInterval: (slot.config?.slideInterval || 5000) / 1000,
2505
- ...slot.config
2506
- }, trackEventCallback);
2800
+ sliderElement = CarouselSliderManager.createSliderContainer(slot, advertisements, optimizedSliderOptions, trackEventCallback);
2507
2801
  if (this._config?.debug) {
2508
- console.log(`๐ŸŽ  Carousel slider created for ${slot.adType} slot: ${slot.id} with ${advertisements.length} ads`);
2802
+ console.log(`๐ŸŽ  Carousel slider created for ${slot.adType} slot: ${slot.id} with ${advertisements.length} ads (optimized: ${slot.optimizedHeight || 'default'})`);
2509
2803
  }
2510
2804
  }
2511
2805
  // ๊ธฐ์กด ๋‚ด์šฉ ์ œ๊ฑฐํ•˜๊ณ  ์Šฌ๋ผ์ด๋” ์ถ”๊ฐ€
@@ -2532,19 +2826,23 @@ class AdsModule {
2532
2826
  // ๊ธฐ๋ณธ HTML ๊ตฌ์กฐ ์ƒ์„ฑ
2533
2827
  const adElement = document.createElement('div');
2534
2828
  adElement.className = 'adstage-ad';
2535
- adElement.style.width = typeof slot.width === 'string' ? slot.width : `${slot.width}px`;
2536
- adElement.style.height = typeof slot.height === 'string' ? slot.height : `${slot.height}px`;
2829
+ // ์Šค๋งˆํŠธํ•œ ํฌ๊ธฐ ์„ค์ • - ์ตœ์ ํ™”๋œ ํฌ๊ธฐ๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ
2830
+ const optimizedHeight = slot.optimizedHeight;
2831
+ const containerElement = container.parentElement || container;
2832
+ if (optimizedHeight) {
2833
+ adElement.style.width = '100%';
2834
+ adElement.style.height = optimizedHeight;
2835
+ }
2836
+ else {
2837
+ const { width, height } = this.calculateAdSize(containerElement, slot.adType, slot.config || {});
2838
+ adElement.style.width = width;
2839
+ adElement.style.height = height;
2840
+ }
2537
2841
  // ๊ด‘๊ณ  ํƒ€์ž…๋ณ„ ๋ Œ๋”๋ง
2538
2842
  switch (slot.adType) {
2539
2843
  case AdType.BANNER:
2540
2844
  if (ad.imageUrl) {
2541
- const img = document.createElement('img');
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);
2845
+ await this.renderOptimizedBannerImage(adElement, ad, slot);
2548
2846
  }
2549
2847
  break;
2550
2848
  case AdType.TEXT:
@@ -2579,6 +2877,50 @@ class AdsModule {
2579
2877
  container.innerHTML = '';
2580
2878
  container.appendChild(adElement);
2581
2879
  }
2880
+ /**
2881
+ * ์ตœ์ ํ™”๋œ ๋ฐฐ๋„ˆ ์ด๋ฏธ์ง€ ๋ Œ๋”๋ง
2882
+ */
2883
+ async renderOptimizedBannerImage(adElement, ad, slot) {
2884
+ try {
2885
+ // ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ •๋ณด ๋กœ๋“œ
2886
+ const imageDimensions = await this.loadImageDimensions(ad.imageUrl);
2887
+ const imageAspectRatio = imageDimensions.width / imageDimensions.height;
2888
+ // ์ปจํ…Œ์ด๋„ˆ ๋น„์œจ ๊ณ„์‚ฐ
2889
+ const containerAspectRatio = slot.aspectRatio || 16 / 9;
2890
+ // ์ด๋ฏธ์ง€ ์š”์†Œ ์ƒ์„ฑ
2891
+ const img = document.createElement('img');
2892
+ img.src = ad.imageUrl;
2893
+ img.alt = ad.title;
2894
+ img.style.width = '100%';
2895
+ img.style.height = '100%';
2896
+ // ๐ŸŽจ ์ตœ์ ํ™”๋œ ์Šคํƒ€์ผ ์ ์šฉ
2897
+ this.applyOptimizedImageStyle(img, imageAspectRatio, containerAspectRatio);
2898
+ // ์ด๋ฏธ์ง€ ๋กœ๋“œ ์™„๋ฃŒ ์ฒ˜๋ฆฌ
2899
+ img.onload = () => {
2900
+ if (this._config?.debug) {
2901
+ console.log(`๐Ÿ–ผ๏ธ Optimized banner image loaded: ${imageDimensions.width}x${imageDimensions.height} (ratio: ${imageAspectRatio.toFixed(2)})`);
2902
+ }
2903
+ };
2904
+ // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
2905
+ img.onerror = () => {
2906
+ console.warn(`Failed to load banner image: ${ad.imageUrl}`);
2907
+ // ํด๋ฐฑ ํ…์ŠคํŠธ ํ‘œ์‹œ
2908
+ adElement.innerHTML = `<div style="display: flex; align-items: center; justify-content: center; background: #f0f0f0; color: #666;">${ad.title}</div>`;
2909
+ };
2910
+ adElement.appendChild(img);
2911
+ }
2912
+ catch (error) {
2913
+ console.warn('Failed to optimize banner image, using fallback:', error);
2914
+ // ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ๋ Œ๋”๋ง (ํด๋ฐฑ)
2915
+ const img = document.createElement('img');
2916
+ img.src = ad.imageUrl;
2917
+ img.alt = ad.title;
2918
+ img.style.width = '100%';
2919
+ img.style.height = '100%';
2920
+ img.style.objectFit = 'cover';
2921
+ adElement.appendChild(img);
2922
+ }
2923
+ }
2582
2924
  /**
2583
2925
  * ๊ด‘๊ณ  ์Šฌ๋กฏ ์ƒˆ๋กœ๊ณ ์นจ
2584
2926
  */
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
  */