Skip to content
Snippets Groups Projects
VizTitle.vue 9.27 KiB
Newer Older
  • Learn to ignore specific revisions
  •   <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;
    }