Skip to content
Snippets Groups Projects
VizTitle.vue 9.27 KiB
Newer Older
  <header id="grid-container">
    <div id="image-container">
Cee Nell's avatar
Cee Nell committed
      <picture>
        <!-- Serve WebP images  -->
        <source 
          type="image/webp"
          srcset="
            @/assets/images/responsive_images/hero_no-faces_5-320.webp 320w,
            @/assets/images/responsive_images/hero_no-faces_5-640.webp 640w,
            @/assets/images/responsive_images/hero_no-faces_5-1280.webp 1280w,
            @/assets/images/responsive_images/hero_no-faces_5-1920.webp 1920w"
          sizes="(max-width: 600px) 320px, 
                (max-width: 1200px) 640px, 
                (min-width: 1201px) 1280px, 
                1920px"
        />

        <!-- Fallback to JPG images for browsers that do not support WebP -->
        <img
          ref="heroImage"
          id="title-image"
          :class="{ mobile: mobileView }"
          srcset="
            @/assets/images/responsive_images/hero_no-faces_5-320.jpg 320w,
            @/assets/images/responsive_images/hero_no-faces_5-640.jpg 640w,
            @/assets/images/responsive_images/hero_no-faces_5-1280.jpg 1280w,
            @/assets/images/responsive_images/hero_no-faces_5-1920.jpg 1920w"
          sizes="(max-width: 600px) 320px, 
                (max-width: 1200px) 640px, 
                (min-width: 1201px) 1280px, 
                1920px"
          src="@/assets/images/responsive_images/hero_no-faces_5-1280.jpg"
Azadpour, Elmera's avatar
Azadpour, Elmera committed
          alt="Two contrasting images of water sources with the left image shows a close-up of a hand holding a clear glass being filled with water from a kitchen faucet. The right image shows a rustic landscape with a blue well tank labeled WELL-X-TROL with a wired fence and dry grass in the background."
Cee Nell's avatar
Cee Nell committed
          @load="onImageLoad"
        />
      </picture>
      <img
        ref="bwHeroImage"
        id="bw-title-image"
Cee Nell's avatar
Cee Nell committed
        rel="preload"
        :class="{ mobile: mobileView }"
Cee Nell's avatar
Cee Nell committed
        srcset="
          @/assets/images/responsive_images/hero_no-faces_5-320.jpg 320w,
          @/assets/images/responsive_images/hero_no-faces_5-640.jpg 640w,
          @/assets/images/responsive_images/hero_no-faces_5-1280.jpg 1280w,
          @/assets/images/responsive_images/hero_no-faces_5-1920.jpg 1920w"
        sizes="(max-width: 600px) 320px, 
              (max-width: 1200px) 640px, 
              (min-width: 1201px) 1280px, 
              1920px"
        src="@/assets/images/responsive_images/hero_no-faces_5-1280.jpg"
Azadpour, Elmera's avatar
Azadpour, Elmera committed
        alt="Two contrasting grayscale images of water sources with the left image shows a close-up of a hand holding a glass being filled with water from a kitchen faucet. The right image shows a rustic landscape with a  well tank labeled WELL-X-TROL with a wired fence and dry grass in the background."
Cee Nell's avatar
Cee Nell committed
      <picture>
        <!-- Serve WebP images  -->
        <source 
          type="image/webp"
          srcset="
            @/assets/images/responsive_images/hero_no-faces_5-320.webp 320w,
            @/assets/images/responsive_images/hero_no-faces_5-640.webp 640w,
            @/assets/images/responsive_images/hero_no-faces_5-1280.webp 1280w,
            @/assets/images/responsive_images/hero_no-faces_5-1920.webp 1920w"
          sizes="(max-width: 600px) 320px, 
                (max-width: 1200px) 640px, 
                (min-width: 1201px) 1280px, 
                1920px"
        />

        <!-- Fallback to JPG images for browsers that do not support WebP -->
        <img
          ref="bwHeroImage"
          id="bw-title-image"
          rel="preload"
          :class="{ mobile: mobileView }"
          srcset="
            @/assets/images/responsive_images/hero_no-faces_5-320.jpg 320w,
            @/assets/images/responsive_images/hero_no-faces_5-640.jpg 640w,
            @/assets/images/responsive_images/hero_no-faces_5-1280.jpg 1280w,
            @/assets/images/responsive_images/hero_no-faces_5-1920.jpg 1920w"
          sizes="(max-width: 600px) 320px, 
                (max-width: 1200px) 640px, 
                (min-width: 1201px) 1280px, 
                1920px"
          src="@/assets/images/responsive_images/hero_no-faces_5-1280.jpg"
Cee Nell's avatar
Cee Nell committed
          alt=""
Cee Nell's avatar
Cee Nell committed
          @load="onImageLoad"
        />
      </picture>
      <!-- Using SVG masks with a circle shape to reveal color image where circles are on top of bw -->
Cee Nell's avatar
Cee Nell committed
      <svg ref="overlay" id="overlay" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <mask id="circleMask">
            <rect width="100%" height="100%" fill="white" />
            <!-- Circles will be added here via D3.js -->
          </mask>
Cee Nell's avatar
Cee Nell committed
        </defs>
Cee Nell's avatar
Cee Nell committed
        <rect width="100%" height="100%" fill="transparent" mask="url(#circleMask)" />
Cee Nell's avatar
Cee Nell committed
      </svg>
    </div>
Cee Nell's avatar
Cee Nell committed
    <div id="text-container">
      <h1>{{ t('text.pageTitle') }}</h1>
      <h3>{{ t('text.pageSubtitle') }}</h3>
Cee Nell's avatar
Cee Nell committed
    </div>
    <div class="text-container" id="lang-button">
      <LanguageButton />
    </div>
Cee Nell's avatar
Cee Nell committed
<script setup>
import { ref, onMounted, nextTick } from 'vue';
Cee Nell's avatar
Cee Nell committed
import { isMobile } from 'mobile-device-detect';
import * as d3 from 'd3';
import { useI18n } from 'vue-i18n';
import LanguageButton from '@/components/LanguageButton.vue';
const { t } = useI18n();
Cee Nell's avatar
Cee Nell committed
const mobileView = ref(isMobile);
Cee Nell's avatar
Cee Nell committed
const heroImage = ref(null);
const bwHeroImage = ref(null);
Cee Nell's avatar
Cee Nell committed
const overlay = ref(null);

const updateSvgDimensions = () => {
  if (heroImage.value && bwHeroImage.value && overlay.value) {
    const { width, height } = heroImage.value.getBoundingClientRect();
Cee Nell's avatar
Cee Nell committed
    overlay.value.setAttribute('width', width);
    overlay.value.setAttribute('height', height);
    bwHeroImage.value.style.width = `${width}px`;
    bwHeroImage.value.style.height = `${height}px`;
    bwHeroImage.value.style.top = '0';
    bwHeroImage.value.style.left = '0';
Cee Nell's avatar
Cee Nell committed
  }
};

Cee Nell's avatar
Cee Nell committed
const addRandomCircles = () => {
  nextTick(() => {
    const svg = d3.select(overlay.value);
    const mask = svg.select('#circleMask');
Cee Nell's avatar
Cee Nell committed
    const svgWidth = overlay.value.clientWidth;
    const svgHeight = overlay.value.clientHeight;

    if (svgWidth === 0 || svgHeight === 0) {
      console.error('SVG dimensions are not set correctly.');
      return;
    }

    mask.selectAll('circle').remove(); // Clear existing circles

    const numberOfCircles = 40; // Number of circles to add
    const circlesData = Array.from({ length: numberOfCircles }).map(() => ({
      cx: Math.random() * svgWidth,
      cy: Math.random() * svgHeight,
      r: Math.random() * (svgWidth / 20)
    }));

    const circles = mask.selectAll('circle')
      .data(circlesData)
      .enter()
      .append('circle')
      .attr('cx', d => d.cx)
      .attr('cy', d => d.cy)
      .attr('r', d => d.r)
      .attr('fill', 'black');

    function animateCircles() {
      circles.transition()
        .duration(1000 + Math.random() * 1000) 
        .ease(d3.easeSin)
        .attr('cy', d => d.cy + (Math.random() - 0.5) * 100) // Larger random range for more movement
        .attr('cx', d => d.cx + (Math.random() - 0.5) * 350)
        .on('end', function() {
          d3.select(this)
            .transition()
            .duration(2000 + Math.random() * 3000) 
            .ease(d3.easeCubic)
            .attr('cy', d => d.cy + (Math.random() - 0.5) * 200)
            .attr('cx', d => d.cx + (Math.random() - 0.5) * 300)
            .on('end', animateCircles); // Recursive call for continuous animation
        });
Cee Nell's avatar
Cee Nell committed
    }

    animateCircles();
Cee Nell's avatar
Cee Nell committed
  });
};

const onImageLoad = () => {
  nextTick(() => {
    updateSvgDimensions();
    addRandomCircles();
  });
Cee Nell's avatar
Cee Nell committed
};

Cee Nell's avatar
Cee Nell committed
onMounted(() => {
  const debounceResize = debounce(() => {
Cee Nell's avatar
Cee Nell committed
    updateSvgDimensions();
    addRandomCircles();
  }, 200);

  window.addEventListener('resize', debounceResize);
Cee Nell's avatar
Cee Nell committed

  if (heroImage.value.complete) {
    onImageLoad();
  } else {
    heroImage.value.addEventListener('load', onImageLoad);
  }
Cee Nell's avatar
Cee Nell committed
});

function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}
<style lang="scss" scoped>
#grid-container {
  display: grid;
  grid-template-rows: auto auto;
Cee Nell's avatar
Cee Nell committed
  width: 100s;
Cee Nell's avatar
Cee Nell committed

#image-container {
  position: relative;
  width: 100%;
}

#title-image,
#bw-title-image {
  width: 100%;
  display: block;
#bw-title-image {
  filter: grayscale(100%);
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none; /* Ensure the grayscale image doesn't interfere with other interactions */
  mask: url(#circleMask); /* Apply the SVG mask */
Cee Nell's avatar
Cee Nell committed
}

Cee Nell's avatar
Cee Nell committed
#overlay {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none; /* Ensure the SVG overlay doesn't interfere with other interactions */
}

#text-container {
  width: 100%;
Hayley Corson-Dosch's avatar
Hayley Corson-Dosch committed
  background: var(--color-background-header-footer);
  color: var(--color-text);
Cee Nell's avatar
Cee Nell committed
  padding-bottom: 20px;
  padding-top: 20px;
  box-sizing: border-box;
Cee Nell's avatar
Cee Nell committed
}
Cee Nell's avatar
Cee Nell committed

Cee Nell's avatar
Cee Nell committed
#image-container {
Hayley Corson-Dosch's avatar
Hayley Corson-Dosch committed
  color: var(--color-text);
  background: var(--color-background-header-footer);
Cee Nell's avatar
Cee Nell committed
}
Cee Nell's avatar
Cee Nell committed

Cee Nell's avatar
Cee Nell committed
h1 {
  margin: -20px 20px;
  color: white;
  text-align: left; /* Optional: Align text to the left */
}
Cee Nell's avatar
Cee Nell committed

Cee Nell's avatar
Cee Nell committed
  margin: 20px 20px 0px 20px;
  color: white;
  text-align: left; /* Optional: Align text to the left */
Cee Nell's avatar
Cee Nell committed
}
Cee Nell's avatar
Cee Nell committed

Cee Nell's avatar
Cee Nell committed
svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
Cee Nell's avatar
Cee Nell committed
}
#lang-button {
  display: flex; 
  justify-content: flex-end; 
  width: 100%;
  margin-bottom: 0;
  padding: 10px;     
  box-sizing: border-box;
}