Skip to main content

Overview

MYMUSICK features dynamic theming that extracts the dominant color from song thumbnails and applies it as a background to song cards on hover. This creates a visually cohesive and immersive user experience.

Color Detection System

The detectarColor() function uses the HTML5 Canvas API to analyze image pixels:
index.html:177-210
function detectarColor(imgURL, element) {
  const img = new Image();
  img.crossOrigin = "anonymous";
  img.src = imgURL;

  img.onload = () => {
    canvas.width = img.width / 4;
    canvas.height = img.height / 4;
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

    const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
    const colors = {};
    let max = 0;
    let dominant = "0,0,0";

    for (let i = 0; i < data.length; i += 16) {
      const r = data[i];
      const g = data[i+1];
      const b = data[i+2];

      if (r+g+b < 60 || r+g+b > 720) continue;

      const rgb = `${r},${g},${b}`;
      colors[rgb] = (colors[rgb] || 0) + 1;

      if (colors[rgb] > max) {
        max = colors[rgb];
        dominant = rgb;
      }
    }

    element.style.background = `rgba(${dominant}, 0.35)`;
  };
}

Canvas Element

A hidden canvas is used for pixel analysis:
index.html:71
<canvas id="canvas" class="hidden"></canvas>

Canvas Context

index.html:87-88
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
The 2D rendering context (ctx) provides methods for drawing and reading pixel data.

How It Works

Step 1: Image Loading

const img = new Image();
img.crossOrigin = "anonymous";
img.src = imgURL;
crossOrigin = "anonymous" is crucial for loading images from external domains (YouTube thumbnails) without CORS errors.

Step 2: Image Downsampling

canvas.width = img.width / 4;
canvas.height = img.height / 4;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
Why downsample?
  • Reduces the number of pixels to analyze (4× smaller in each dimension = 16× fewer pixels)
  • Improves performance significantly
  • Provides accurate dominant color representation
  • Full Resolution (480×360): 172,800 pixels to analyze
  • Downsampled (120×90): 10,800 pixels to analyze
  • Performance Gain: ~16× faster processing

Step 3: Pixel Data Extraction

const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
The getImageData() method returns a Uint8ClampedArray containing RGBA values:
[r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, ...]

Step 4: Color Frequency Analysis

const colors = {};
let max = 0;
let dominant = "0,0,0";

for (let i = 0; i < data.length; i += 16) {
  const r = data[i];
  const g = data[i+1];
  const b = data[i+2];

  if (r+g+b < 60 || r+g+b > 720) continue;

  const rgb = `${r},${g},${b}`;
  colors[rgb] = (colors[rgb] || 0) + 1;

  if (colors[rgb] > max) {
    max = colors[rgb];
    dominant = rgb;
  }
}

Loop Optimization

for (let i = 0; i < data.length; i += 4) {
  // Process every pixel
}
Why i += 16?
  • Each pixel is 4 bytes (RGBA)
  • Incrementing by 16 samples every 4th pixel
  • Further reduces processing time by 4×

Color Filtering

if (r+g+b < 60 || r+g+b > 720) continue;
This condition filters out:
  • Too dark colors (sum < 60): Near-black pixels that lack visual impact
  • Too light colors (sum > 720): Near-white pixels that would wash out the design
RGB values range from 0-255 per channel. Sum ranges:
  • Pure black: 0 + 0 + 0 = 0
  • Pure white: 255 + 255 + 255 = 765
  • Filter range: 60-720 (captures vibrant mid-tones)

Frequency Counting

const rgb = `${r},${g},${b}`;
colors[rgb] = (colors[rgb] || 0) + 1;

if (colors[rgb] > max) {
  max = colors[rgb];
  dominant = rgb;
}
The algorithm:
  1. Creates a string key from RGB values (e.g., “120,45,200”)
  2. Increments the count for that color
  3. Tracks the color with the highest frequency
  4. Updates dominant when a new maximum is found

Step 5: Apply Background Color

element.style.background = `rgba(${dominant}, 0.35)`;
The dominant color is applied with 35% opacity to:
  • Maintain text readability
  • Create a subtle, elegant effect
  • Avoid overwhelming the design

Event Integration

The color detection is triggered by mouse hover events:
index.html:163-164
div.addEventListener("mouseenter", () => detectarColor(song.thumbnail, div));
div.addEventListener("mouseleave", () => div.style.background = "");

Hover Behavior

1

Mouse Enter

User hovers over a song card → detectarColor() is called
2

Color Extraction

Canvas API analyzes the thumbnail and determines dominant color
3

Background Applied

Card background smoothly transitions to the dominant color with 35% opacity
4

Mouse Leave

User moves mouse away → background is cleared

CORS Configuration

Both the Image object and HTML img elements use CORS:
img.crossOrigin = "anonymous";
index.html:153-156
<img src="${song.thumbnail}" 
     alt="Portada de ${song.title}"
     loading="lazy"
     crossorigin="anonymous">
Without crossOrigin="anonymous", the canvas would be “tainted” by external images, and getImageData() would throw a security error.

Visual Effects

The dynamic background combines with existing hover effects:
estilooriginal.css:204-208
.song:hover {
  background: #1e1e1e;
  border-color: var(--accent);
  transform: translateY(-6px);
}

Combined Effect

  1. Base hover background: #1e1e1e (dark gray)
  2. Dynamic color overlay: rgba(r, g, b, 0.35) from dominant color
  3. Border highlight: Accent red border appears
  4. Lift animation: Card rises 6px upward
transition: 0.2s ease;
All hover effects (background, border, transform) animate smoothly over 200ms with an ease timing function.

Performance Optimization Summary

Image Downsampling

Reduces image to 25% size (16× fewer pixels)

Pixel Sampling

Analyzes every 4th pixel (4× fewer operations)

Color Filtering

Skips extreme dark/light pixels (faster iteration)

Deferred Execution

Only runs on hover (not during initial render)

Total Performance Gain

  • Original pixels: 480×360 = 172,800
  • After downsampling: 120×90 = 10,800 (16× reduction)
  • After sampling: ~2,700 pixels (64× total reduction)

Usage Example

// When rendering song cards
songs.forEach(song => {
  const div = document.createElement("div");
  div.className = "song";
  
  // Attach hover event
  div.addEventListener("mouseenter", () => 
    detectarColor(song.thumbnail, div)
  );
  
  div.addEventListener("mouseleave", () => 
    div.style.background = ""
  );
  
  results.appendChild(div);
});

Color Theory Application

By filtering out colors with RGB sums < 60 or > 720, MYMUSICK ensures that only vibrant, saturated colors are used for theming—avoiding muddy browns, washed-out grays, and near-black/white values.

RGB Sum Ranges

RangeColor TypeUsed?
0-59Very dark (blacks, dark grays)❌ No
60-720Vibrant mid-tonesYes
721-765Very light (whites, pale colors)❌ No

Music Search

See how song cards are rendered and made interactive

Responsive Design

Learn how visual effects adapt to mobile devices

Build docs developers (and LLMs) love