Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
165 views
in Technique[技术] by (71.8m points)

javascript - Start Css animation on hover in a Svg used as border-source

I have an element bordered with an Svg:

.element-bordered-with-svg {

  border-image-source: url('images/border.svg');
  [....]
}

Inside the border.svg there is a Css animation (defined in the <style> tag), like so:

<svg class="svg-frame-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 337 198">
 
   <style>
     .svg-frame-1:hover > path {
       animation-play-state: running;
     }

     path {
       stroke:#BEA757;
       fill-opacity:0;
       stroke-width:1;
       stroke-dasharray: 1948;
       stroke-dashoffset:1948;
       animation-name: dash1;
       animation-duration: 2s;
       animation-fill-mode: forwards;
       animation-delay: 2s;  
       animation-play-state: paused; 
      }

   @keyframes dash1 {
      0%  { stroke-dashoffset:1948;}
     100%{stroke-dashoffset:0;}
    } 

  </style>

  [...]

</svg>
  

I would like to start the animation only when I hover the .element-bordered-with-svg element.

Of course, because it is not inlined, the <svg> knows nothing about the elements of the main DOM and vice versa.

Is there a possible solution, perhaps in Javascript, to this problem?

EDITED: following the advice of @Danny '365CSI' Engelman in the comments I've tried with this solution (see the snippet):

function docReady(fn) {
    // see if DOM is already available
    if (
      document.readyState === 'complete' ||
      document.readyState === 'interactive'
    ) {
      // call on next available tick
      setTimeout(fn, 1);
    } else {
      document.addEventListener('DOMContentLoaded', fn);
    }
  }
  function escapeRegExp(str) {
    return str.replace(/([.*+?^=!:${}()|[]/\])/g, '\$1');
  }

  function replaceAll(str, find, replace) {
    return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
  }

  function transformInDataUri(id) {
    var svgText = new XMLSerializer().serializeToString(
      document.getElementById(id),
    );
    var raw = svgText;
    var encoded = raw.replace(/s+/g, ' ');

    // According to Taylor Hunt, lowercase gzips better ... my tiny test confirms this
    encoded = replaceAll(encoded, '%', '%25');
    encoded = replaceAll(encoded, '> <', '><'); // normalise spaces elements
    encoded = replaceAll(encoded, '; }', ';}'); // normalise spaces css
    encoded = replaceAll(encoded, '<', '%3c');
    encoded = replaceAll(encoded, '>', '%3e');
    encoded = replaceAll(encoded, '"', "'");
    encoded = replaceAll(encoded, '#', '%23'); // needed for ie and firefox
    encoded = replaceAll(encoded, '{', '%7b');
    encoded = replaceAll(encoded, '}', '%7d');
    encoded = replaceAll(encoded, '|', '%7c');
    encoded = replaceAll(encoded, '^', '%5e');
    encoded = replaceAll(encoded, '`', '%60');
    encoded = replaceAll(encoded, '@', '%40');
    var uri = 'url("data:image/svg+xml;charset=UTF-8,' + encoded + '")';
    return uri;
  }

  function getCurrentAnimatePropertyValue(el, propertyName) {
    let value = getComputedStyle(el, null).getPropertyValue(propertyName);

    return value;
  }

  function getTimer() {
    console.log('ok')
  
    let elapsedTime = 0;
    let timer;
  
    function start() {
          timer = window.setInterval(function() {
            elapsedTime += 100
            console.log(elapsedTime)
          }, 100)
     }
    
    function pause() {
      window.clearInterval(timer)
      console.log(elapsedTime)
      return elapsedTime
    }
  
    function reset() {
      window.clearInterval(timer)
      elapsedTime = 0;
      console.log(elapsedTime)
      return elapsedTime
    }
  
    
  
  
    
    return {
      start: start,
      pause: pause,
      reset: reset
    }
  
  }
  
  
  function getUpdatedDurations(currentDurations, elapsedTime) {
    let animationDurations = currentDurations; // '2s, 1s';
    let values = animationDurations.replace(/s/g, "").split(',').map(el => { 
      let finalEl = parseFloat(el.replace("s", "")) * 1000;
      return finalEl;
    })
    let updatedValues = values.map(duration => duration - elapsedTime )
    
    let updatedValuesAsString = updatedValues.map(duration => {
      
      return (duration/1000).toString(10) + 's';
    })
    return updatedValuesAsString.toString()
  }
  

  // svg, [{ 'dash1': {'dash-offset': '1000px' }}, { 'fill': {'fill-opacity': 1 }}]
  function changeAnimationStartKeyFrame(animations) {
    
    for (let index = 0; index <document.styleSheets.length; index++) {
      let stylesheet = document.styleSheets[index];
      if(stylesheet['title'] === 'svg') {
        animations.map(animation => {
      
          let animationName = Object.keys(animation)[0];
  
          let cssRules = stylesheet['cssRules']
  
  
          let objectWithRules = animation[animationName];
          
          let propertyName = Object.keys(objectWithRules)[0]
          let updatedValue = objectWithRules[propertyName];
  
          for (let index = 0; index < cssRules.length; index++) {
            let cssRule  = cssRules[index];
           
            if(cssRule.type === 7 && cssRule.name === animationName) {
              console.log(propertyName)
              let CSSKeyframesRule = cssRule; 
    
              if(animationName === 'dash1') {
                CSSKeyframesRule.deleteRule("0%");
                CSSKeyframesRule.appendRule(`0% { ${propertyName}: ${updatedValue}; }`);
              }
              else {
                CSSKeyframesRule.deleteRule("80%");
                CSSKeyframesRule.appendRule(`80% { ${propertyName}: ${updatedValue}; }`);
              }
              
            }
            
          }
      
      })
      }
      console.log(stylesheet)
    }

   
   
    
  }

  docReady(function () {
    // charset reportedly not needed ... I need to test before implementing

    console.log(Array.from(document.styleSheets))

    var svgAsBorderSelector = 'svg-as-border';
    var svg = document.getElementById(svgAsBorderSelector);
    var svgAnimatedSelectors = '.path-1';
    var svgElementsToAnimate = svg.querySelectorAll(svgAnimatedSelectors);

    let divToBorderWithAnimatedSvg = document.querySelector('.frame-1');

    divToBorderWithAnimatedSvg.style.borderImageSource = transformInDataUri(
      svgAsBorderSelector,
    );

    let currentFillOpacity;
    let currentStrokeDashOffset;
    let currentStrokeDashArray;
    let timer = getTimer(), elapsedTime;

    divToBorderWithAnimatedSvg.addEventListener('mouseenter', (e) => {
      for (var i = 0, max = svgElementsToAnimate.length; i < max; i++) {
        let element = svgElementsToAnimate[i];

        currentStrokeDashOffset = getCurrentAnimatePropertyValue(
          element,
          'stroke-dashoffset',
        );

        currentStrokeDashArray = getCurrentAnimatePropertyValue(
          element,
          'stroke-dasharray',
        );

        currentFillOpacity = getCurrentAnimatePropertyValue(
          element,
          'fill-opacity',
        );
        document.body.clientHeight
        element.style.webkitAnimationName = 'dash1';
        timer.start();
        element.style.strokeDashoffset = currentStrokeDashOffset;
        element.style.strokeDasharray = currentStrokeDashArray;
        element.style.fillOpacity = currentFillOpacity;
        element.style.animationPlayState = 'running, running';
      }

      divToBorderWithAnimatedSvg.style.borderImageSource = transformInDataUri(
        svgAsBorderSelector,
      );
    });

    divToBorderWithAnimatedSvg.addEventListener('mouseleave', (e) => {
      for (var i = 0, max = svgElementsToAnimate.length; i < max; i++) {
        let element = svgElementsToAnimate[i];
        

        currentStrokeDashOffset = getCurrentAnimatePropertyValue(
          element,
          'stroke-dashoffset',
        );

        currentStrokeDashArray = getCurrentAnimatePropertyValue(
          element,
          'stroke-dasharray',
        );

        currentFillOpacity = getCurrentAnimatePropertyValue(
          element,
          'fill-opacity',
        );
     
        let currentAnimationTime = getCurrentAnimatePropertyValue(element, 'animation-duration');
        elapsedTime = timer.pause();
        let updatedAnimationTime = getUpdatedDurations(currentAnimationTime, elapsedTime)
        element.style.animationPlayState = 'paused, paused';
        element.style.webkitAnimationName = 'none';
        element.style.strokeDashoffset = currentStrokeDashOffset;
        element.style.strokeDasharray = currentStrokeDashArray;
        element.style.fillOpacity = currentFillOpacity;
        element.style.animationDuration = updatedAnimationTime;
        changeAnimationStartKeyFrame([{ 'dash1': {'stroke-dashoffset': currentStrokeDashOffset }}, { 'fill': {'fill-opacity': currentFillOpacity }}])     

      }
      divToBorderWithAnimatedSvg.style.borderImageSource = transformInDataUri(
        svgAsBorderSelector,
      );
    });
  });
.frame-1 {
    border: 22px solid;
    border-image-slice: 41;
    border-image-width: 32px;
    border-image-outset: 0;
    border-image-repeat: stretch;
    }

.blockquote-container blockquote, .blockquote-container blockquote p {
    color: #a17c4a;
}

.blockquote-container blockquote {
    padding: .5em;
}
<div class="blockquote-container">
        <blockquote class="frame-1">
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec placerat ex enim, nec tempus nisl commodo a. Nullam eu odio ut neque interdum mollis quis ac velit. Etiam pulvinar aliquam auctor.</p>

        </blockquote>
    </div>
 <svg  id='svg-as-border' class='svg-frame-1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 337 198'>
        <g fill='none' fill-rule='evenodd'>
            <style title="svg">
                .path-1 {
                    stroke: #BEA757;
                    fill-opacity: 0;
                    stroke-width: 1;
                    stroke-dasharray: 1948px;
                    stroke-dashoffset: 1948px;
                    animation-name: dash1, fill;
                    animation-duration: 2s, 1s;
                    animation-fill-mode: forwards;
                    animation-delay: 0s, 0s;
                    animation-play-state: paused, paused;

                }




                @keyframes dash1 {
                    0% {
                        stroke-dashoffset: 1948px;
                    }

                    100% {
                        stroke-dashoffset: 0;
                    }
                }



                @keyframes fill {
                    80% {
                        fill-opacity: 0;
                    }

                    100% {
                        fill-opacity: 1;
                    }
                }
            </style>
            <path class='path-1' fill='#BEA757'
                d='M22.68,176.6 L315.68,176.6 L315.68,22.6 L22.68,22.6 L22.68,176.6 Z M331.596839,180.6 L332.68,180.6 L332.68,193.6 L319.68,193.6 L319.68,180.6 L3

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Create your own Custom Element <bordered-svg> (supported in all modern Browsers) that dynamically creates the SVG with all unique values, no need for shadowDOM then.

If you add shadowDOM the CSS gets simpler, but controlling it from main DOM becomes more difficult.

<style>
  div { width: 200px;display: grid;grid-template-columns: 1fr 1fr }
  svg { width: 100%;vertical-align:top;cursor:pointer }
</style>
<div>
  <bordered-svg color="red"></bordered-svg>
  <bordered-svg color="gold"></bordered-svg>
  <bordered-svg color="green"></bordered-svg>
  <bordered-svg color="rebeccapurple"></bordered-svg>
</div>
<script>
  customElements.define("bordered-svg", class extends HTMLElement {
    connectedCallback() {
      let uniqueID = "bordered" + Math.random().toString().substr(2, 8);
      this.innerHTML = `
        <svg id="${uniqueID}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 6">
        <style>
        #${uniqueID} path {
          stroke:${this.getAttribute("color")||"grey"};
          stroke-width:3; stroke-dasharray: 12;
          animation-name: animation${uniqueID};
          animation-duration: 5s;
          animation-play-state: paused; 
        }
        @keyframes animation${uniqueID} {
          0%  { stroke-dashoffset:0 }
          100%{ stroke-dashoffset:1000 }
        } 
        </style>
        <path fill='none' d='M0 0h6v6h-6z'></path></svg>`;
      let style = this.querySelector(`#${uniqueID} path`).style;
      this.onmouseenter = () => style.animationPlayState = "running";
      this.onmouseleave = () => style.animationPlayState = "paused";
    }
  });
</script>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...