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