Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Advanced Displacement Map Creator</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <!-- Left Panel: Drawing Canvas and Controls -->
    <div class="panel left-panel">
      <h2>Create Displacement Map</h2>
      <canvas id="drawing-canvas" width="500" height="500"></canvas>
      <div class="controls">
        <!-- Brush Controls -->
        <div class="control-group">
          <label>Brush Color:</label>
          <button class="color-btn" data-color="#FF0000" style="background-color: #FF0000;"></button>
          <button class="color-btn" data-color="#00FF00" style="background-color: #00FF00;"></button>
          <button class="color-btn" data-color="#0000FF" style="background-color: #0000FF;"></button>
          <button class="color-btn" data-color="#808080" style="background-color: #808080;"></button>
          <input type="color" id="custom-color" value="#FFFFFF" title="Custom Color">
        </div>
        <div class="control-group">
          <label for="brush-size">Brush Size: <span id="brush-size-value">10</span>px</label>
          <input type="range" id="brush-size" min="1" max="50" value="10">
        </div>
        <div class="control-group">
          <label for="brush-opacity">Opacity: <span id="brush-opacity-value">1.0</span></label>
          <input type="range" id="brush-opacity" min="0.1" max="1" step="0.1" value="1">
        </div>

        <!-- Noise Generation Controls -->
        <div class="control-group">
          <label>Generate Noise:</label>
          <button class="noise-btn" data-noise="fractal">Fractal Noise</button>
          <button class="noise-btn" data-noise="turbulence">Turbulence</button>
          <button class="noise-btn" data-noise="stripes">Stripes</button>
          <button class="noise-btn" data-noise="checkerboard">Checkerboard</button>
        </div>

        <!-- Overlay Control -->
        <div class="control-group">
          <label for="overlay-toggle">Overlay with Source Image:</label>
          <input type="checkbox" id="overlay-toggle">
        </div>

        <!-- Action Buttons -->
        <div class="control-group">
          <button id="reset-btn">Reset Canvas</button>
          <button id="download-btn">Download Displacement Map</button>
        </div>
      </div>
    </div>

    <!-- Right Panel: Displacement Effect Preview and Controls -->
    <div class="panel right-panel">
      <h2>Displacement Effect Preview</h2>
      <div class="preview-container">
        <img id="preview-image" src="https://i.ibb.co/2sxT6jZ/Retro-80s-Human-Flying-Poster-cropped.jpg" alt="Preview Image">
      </div>
      <div class="controls">
        <div class="control-group">
          <label for="displacement-scale">Displacement Scale: <span id="displacement-scale-value">20</span></label>
          <input type="range" id="displacement-scale" min="0" max="100" value="20">
        </div>
      </div>
    </div>
  </div>

  <!-- SVG Filter Definition -->
  <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
    <defs>
      <filter id="displacementFilter">
        <!-- feImage will be updated with the displacement map -->
        <feImage id="displacement-map" xlink:href="" result="displacementMap" />
        <feDisplacementMap in="SourceGraphic" in2="displacementMap" scale="20" xChannelSelector="R" yChannelSelector="G" />
      </filter>
    </defs>
  </svg>

  <script src="script.js"></script>
</body>
</html>
              
            
!

CSS

              
                /* Reset and Basic Styles */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 20px;
  background-color: #1e1e1e;
  color: #ffffff;
  font-family: Arial, sans-serif;
}

h2 {
  text-align: center;
  margin-bottom: 10px;
}

/* Container for both panels */
.container {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: center;
}

/* Panel Styles */
.panel {
  background-color: #2e2e2e;
  padding: 20px;
  border-radius: 10px;
  flex: 1 1 500px;
  max-width: 600px;
}

.left-panel, .right-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Canvas Styles */
#drawing-canvas {
  border: 2px solid #555;
  border-radius: 10px;
  background-color: #808080; /* Default gray background */
  cursor: crosshair;
}

/* Preview Image Styles */
.preview-container {
  width: 500px;
  height: 500px;
  overflow: hidden;
  border: 2px solid #555;
  border-radius: 10px;
  background-color: #000;
}

#preview-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: url(#displacementFilter);
}

/* Controls Styles */
.controls {
  margin-top: 20px;
  width: 100%;
}

.control-group {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.control-group label {
  margin-right: 10px;
  flex: 1 1 150px;
}

.control-group input[type="range"] {
  flex: 2 1 200px;
}

.color-btn, .noise-btn {
  width: 30px;
  height: 30px;
  border: none;
  margin-right: 5px;
  cursor: pointer;
  border-radius: 5px;
}

.color-btn.active, .noise-btn.active {
  border: 2px solid #ffffff;
}

#custom-color {
  width: 40px;
  height: 30px;
  border: none;
  cursor: pointer;
}

.noise-btn {
  width: auto;
  padding: 5px 10px;
  margin-right: 5px;
  background-color: #555;
  color: #fff;
  border-radius: 5px;
}

.noise-btn:hover, .noise-btn.active {
  background-color: #777;
}

/* Button Styles */
button {
  padding: 10px 20px;
  background-color: #555;
  border: none;
  border-radius: 5px;
  color: #fff;
  cursor: pointer;
  transition: background-color 0.3s;
  margin-right: 10px;
}

button:hover {
  background-color: #777;
}

button:active {
  background-color: #999;
}

/* Responsive Adjustments */
@media (max-width: 1200px) {
  .preview-container, #drawing-canvas {
    width: 400px;
    height: 400px;
  }
}

@media (max-width: 800px) {
  .preview-container, #drawing-canvas {
    width: 300px;
    height: 300px;
  }
}
              
            
!

JS

              
                document.addEventListener('DOMContentLoaded', () => {
  // Canvas Setup
  const canvas = document.getElementById('drawing-canvas');
  const ctx = canvas.getContext('2d');

  // Initialize canvas with default gray background
  function initializeCanvas() {
    ctx.fillStyle = '#808080';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  }

  initializeCanvas();

  // Drawing State
  let drawing = false;
  let brushColor = '#FFFFFF';
  let brushSize = 10;
  let brushOpacity = 1.0;

  // Set up brush controls
  const colorButtons = document.querySelectorAll('.color-btn');
  const customColorPicker = document.getElementById('custom-color');
  const brushSizeSlider = document.getElementById('brush-size');
  const brushSizeValue = document.getElementById('brush-size-value');
  const brushOpacitySlider = document.getElementById('brush-opacity');
  const brushOpacityValue = document.getElementById('brush-opacity-value');
  const downloadBtn = document.getElementById('download-btn');
  const resetBtn = document.getElementById('reset-btn');
  const noiseButtons = document.querySelectorAll('.noise-btn');
  const overlayToggle = document.getElementById('overlay-toggle');

  // Handle color button clicks
  colorButtons.forEach(btn => {
    btn.addEventListener('click', () => {
      brushColor = btn.getAttribute('data-color');
      // Remove active class from all buttons
      colorButtons.forEach(b => b.classList.remove('active'));
      // Add active class to the selected button
      btn.classList.add('active');
      // Uncheck custom color if any
      customColorPicker.value = '#FFFFFF';
    });
  });

  // Handle custom color picker
  customColorPicker.addEventListener('change', (e) => {
    brushColor = e.target.value;
    // Remove active class from all color buttons
    colorButtons.forEach(b => b.classList.remove('active'));
  });

  // Handle brush size slider
  brushSizeSlider.addEventListener('input', (e) => {
    brushSize = e.target.value;
    brushSizeValue.textContent = brushSize;
  });

  // Handle brush opacity slider
  brushOpacitySlider.addEventListener('input', (e) => {
    brushOpacity = e.target.value;
    brushOpacityValue.textContent = brushOpacity;
  });

  // Handle noise generation buttons
  noiseButtons.forEach(btn => {
    btn.addEventListener('click', () => {
      generateNoise(btn.getAttribute('data-noise'));
      // Remove active class from all noise buttons
      noiseButtons.forEach(b => b.classList.remove('active'));
      // Add active class to the selected button
      btn.classList.add('active');
    });
  });

  // Handle reset button
  resetBtn.addEventListener('click', () => {
    initializeCanvas();
    updateDisplacementMap();
  });

  // Handle overlay toggle
  overlayToggle.addEventListener('change', (e) => {
    const previewImage = document.getElementById('preview-image');
    if (e.target.checked) {
      previewImage.style.opacity = '0.7';
      previewImage.style.mixBlendMode = 'overlay';
    } else {
      previewImage.style.opacity = '1';
      previewImage.style.mixBlendMode = 'normal';
    }
  });

  // Drawing Functions
  function startDrawing(e) {
    drawing = true;
    ctx.beginPath();
    const { x, y } = getCanvasCoordinates(e);
    ctx.moveTo(x, y);
  }

  function draw(e) {
    if (!drawing) return;
    const { x, y } = getCanvasCoordinates(e);
    ctx.lineTo(x, y);
    ctx.strokeStyle = brushColor;
    ctx.lineWidth = brushSize;
    ctx.globalAlpha = brushOpacity;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.stroke();
  }

  function stopDrawing() {
    if (!drawing) return;
    drawing = false;
    ctx.closePath();
    updateDisplacementMap();
  }

  function getCanvasCoordinates(event) {
    const rect = canvas.getBoundingClientRect();
    let x, y;
    if (event.touches) {
      x = event.touches[0].clientX - rect.left;
      y = event.touches[0].clientY - rect.top;
    } else {
      x = event.clientX - rect.left;
      y = event.clientY - rect.top;
    }
    return { x, y };
  }

  // Event Listeners for Drawing
  canvas.addEventListener('mousedown', startDrawing);
  canvas.addEventListener('mousemove', draw);
  canvas.addEventListener('mouseup', stopDrawing);
  canvas.addEventListener('mouseleave', stopDrawing);

  // Touch Events for Mobile Support
  canvas.addEventListener('touchstart', (e) => {
    e.preventDefault();
    startDrawing(e);
  });
  canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    draw(e);
  });
  canvas.addEventListener('touchend', (e) => {
    e.preventDefault();
    stopDrawing();
  });

  // SVG Filter Setup
  const displacementMapElement = document.getElementById('displacement-map');
  const displacementScaleSlider = document.getElementById('displacement-scale');
  const displacementScaleValue = document.getElementById('displacement-scale-value');

  // Update displacement scale
  displacementScaleSlider.addEventListener('input', (e) => {
    const scale = e.target.value;
    displacementScaleValue.textContent = scale;
    const displacementFilter = document.getElementById('displacementFilter');
    const feDisplacementMap = displacementFilter.querySelector('feDisplacementMap');
    feDisplacementMap.setAttribute('scale', scale);
  });

  // Initial displacement map update
  updateDisplacementMap();

  // Function to update the displacement map in the SVG filter
  function updateDisplacementMap() {
    const dataURL = canvas.toDataURL();
    displacementMapElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', dataURL);
  }

  // Download Functionality
  downloadBtn.addEventListener('click', () => {
    const link = document.createElement('a');
    link.download = 'displacement-map.png';
    link.href = canvas.toDataURL();
    link.click();
  });

  // Ensure the displacement map updates when the brush opacity or size changes
  [brushOpacitySlider, brushSizeSlider].forEach(slider => {
    slider.addEventListener('change', updateDisplacementMap);
  });

  // Function to generate different types of noise and patterns
  function generateNoise(type) {
    switch(type) {
      case 'fractal':
        generateFractalNoise();
        break;
      case 'turbulence':
        generateTurbulence();
        break;
      case 'stripes':
        generateStripes();
        break;
      case 'checkerboard':
        generateCheckerboard();
        break;
      default:
        console.warn('Unknown noise type:', type);
    }
    updateDisplacementMap();
  }

  // Generate Fractal Noise
  function generateFractalNoise() {
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    for(let i = 0; i < data.length; i += 4) {
      const value = Math.random() * 255;
      data[i] = value;     // R
      data[i+1] = value;   // G
      data[i+2] = value;   // B
      data[i+3] = 255;     // A
    }
    ctx.putImageData(imageData, 0, 0);
  }

  // Generate Turbulence
  function generateTurbulence() {
    const baseFrequency = 0.05; // Adjust for more or less turbulence
    const numOctaves = 4;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#808080';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Using SVG feTurbulence via offscreen SVG
    const svgNS = "http://www.w3.org/2000/svg";
    const svg = document.createElementNS(svgNS, "svg");
    const feTurbulence = document.createElementNS(svgNS, "feTurbulence");
    feTurbulence.setAttribute("type", "fractalNoise");
    feTurbulence.setAttribute("baseFrequency", baseFrequency);
    feTurbulence.setAttribute("numOctaves", numOctaves);
    feTurbulence.setAttribute("result", "turbulence");
    svg.appendChild(feTurbulence);

    const feImage = document.createElementNS(svgNS, "feImage");
    feImage.setAttribute("xlink:href", canvas.toDataURL());
    feImage.setAttribute("result", "background");
    svg.appendChild(feImage);

    const feBlend = document.createElementNS(svgNS, "feBlend");
    feBlend.setAttribute("in", "turbulence");
    feBlend.setAttribute("in2", "background");
    feBlend.setAttribute("mode", "multiply");
    svg.appendChild(feBlend);

    const svgData = new XMLSerializer().serializeToString(svg);
    const img = new Image();
    img.onload = function() {
      ctx.drawImage(img, 0, 0);
    }
    img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
  }

  // Generate Stripes Pattern
  function generateStripes() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const stripeWidth = 20;
    for(let x = 0; x < canvas.width; x += stripeWidth * 2) {
      ctx.fillStyle = '#FFFFFF';
      ctx.fillRect(x, 0, stripeWidth, canvas.height);
    }
  }

  // Generate Checkerboard Pattern
  function generateCheckerboard() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const size = 40;
    for(let y = 0; y < canvas.height; y += size) {
      for(let x = 0; x < canvas.width; x += size) {
        if((x / size + y / size) % 2 === 0) {
          ctx.fillStyle = '#FFFFFF';
        } else {
          ctx.fillStyle = '#000000';
        }
        ctx.fillRect(x, y, size, size);
      }
    }
  }
});
              
            
!
999px

Console