3

I have rocks in this image:

enter image description here

.rock {
  width: 100%;
  height: 200vh;
}

.rock:before {
  content: '';
  position: absolute;
  z-index: -1;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: url(rock.svg);
  background-size: 384px;
  background-repeat: repeat;
  -webkit-mask-image: linear-gradient(transparent 0%, #fff 10%, #fff 90%, transparent 100%);
  mask-image: linear-gradient(transparent 0%, #fff 10%, #fff 90%, transparent 100%);
  /*box-shadow: inset 0 0 0 2px black;*/
}

And the SVG looks like this essentially:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<g>
  ...
  <path d="M61.639,162.195c0.483-0.863,0.887-1.493,1.114-1.659l-0.859,0.215C61.757,161.203,61.694,161.695,61.639,162.195z"/>
  <path d="M62.753,160.536l0.141-0.035C62.86,160.477,62.812,160.493,62.753,160.536z"/>
  <path d="M53.959,179.342c-2.256,3.034-3.331,7.56-4.521,10.83C50.734,186.206,53.188,183.417,53.959,179.342z"/>
  <path d="M53.959,179.342c0.363-0.488,0.754-0.942,1.184-1.342l-1,0.25C54.104,178.631,54.027,178.984,53.959,179.342z"/>
  <path d="M159.644,110l2-4c-2.467,3.473-4.598,7.94-5.592,11.998C157.587,115.744,158.907,112.613,159.644,110z"/>
  <path d="M117.162,179.287c0.024-0.063,0.059-0.096,0.09-0.138c-0.964-0.864-1.931-1.724-2.905-2.571
    C115.11,177.611,116.044,178.677,117.162,179.287z"/>
  <path d="M117.894,178.75c-0.193,0.177-0.464,0.158-0.642,0.398c1.052,0.944,2.1,1.896,3.142,2.852L117.894,178.75z"/>
  <path d="M182.939,156.556c-0.409,0.524-0.674,1.081-0.725,1.665c0.221,0.015,0.454-0.017,0.692-0.066
    c2.024-0.429,4.557-3.049,5.555-5.277C186.501,153.797,184.119,155.045,182.939,156.556z"/>
  <path d="M188.894,151.25c-0.012,0.482-0.17,1.041-0.432,1.627c0.984-0.461,1.866-0.842,2.432-1.127L188.894,151.25z"/>
</g>
</svg>

I am using it in HTML like this:

<div class='rock'></div>

The question is, how can I instead dig into the SVG and animate each rock individually and separately, so that each one appears to fade in and out at random, sparkling.

Lance
  • 75,200
  • 93
  • 289
  • 503
  • consider an `animate` element inside each path with random values: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate – Temani Afif Jul 30 '19 at 14:15
  • You won't be able to refer to individual rocks within the SVG if you use the SVG as a `background-image`. The image is considered one single entity and CSS cannot affect elements within it. Consider my related answer here: [:hover not working on svg when svg is in external file](https://stackoverflow.com/a/50219017/691711). – zero298 Jul 30 '19 at 14:16
  • I suppose you could potentially use CSS animations **within** the SVG file itself to animate, but you won't be able to control them from the outside. Are you open to emplacing the SVG into your HTML? – zero298 Jul 30 '19 at 14:18
  • How would I do it if I embed the SVG into the HTML? – Lance Jul 30 '19 at 14:20

2 Answers2

2

per @zero298, you can't do this if you use the SVG as a background image, so you'll have to embed it directly in the page. That said, if you embed it directly, it's possible:

First, add an individual id or class to each rock path (I suggest randomly adding five classes; let's call them fast, mediumfast, medium, mediumslow, slow. Then, create a simple keyframes animation in CSS - something like this:

@keyframes fadeInOut {
  start {
    opacity: 1;
  }

  end {
    opacity: 0.8; // Modify this value to change how extreme the fading is
  }
}

Now you need to apply the animation to the rocks. To make it 'twinkle', I'd use a couple of different animation durations so they don't all fade out together. All of them will share some of the same properties, like so:

#yourSVGId path {
  animation: fadeInOut 1s ease infinite alternate;
}

.fast {
  animation-duration: 0.3s;
}

.mediumfast {
  animation-duration: 0.5s;
}

.medium {
  animation-duration: 0.7s;
}

.mediumslow {
  animation-duration: 0.9s;
}

.slow {
  animation-duration: 1.1s;
}
Ian
  • 5,704
  • 6
  • 40
  • 72
  • I like this answer because it deals with CSS animations. I've written an answer that uses CSS transitions instead, but the principle is effectively the same. – zero298 Jul 30 '19 at 14:34
  • Can you show how to actually embed and use the SVG with `` so that it's a repeating seamless texture like in the rock example, so I can use it on multiple separate parts of the page? – Lance Jul 30 '19 at 14:37
  • @LancePollard I think the best way to achieve what you want is to make an animated gif or animated png file and embed that as your background. You can't really tile a dynamically embedded SVG file – Ian Jul 30 '19 at 14:55
2

You won't be able to refer to individual rocks within the SVG if you use the SVG as a background-image. The image is considered one single entity and CSS cannot affect elements within it. Consider my related answer here: :hover not working on svg when svg is in external file.

I would suggest embedding the SVG into the HTML so that you can interact with it as a first class entity. From there you have a lot of different ways to affect your SVG including direct manipulation or using CSS animations or transitions. Consider the example below that uses CSS transitions.

function rand(floor, ceil) {
  return Math.floor(Math.random() * (ceil - floor) + floor);
}

setInterval(() => {
  const circles = document.querySelectorAll("#rocky circle");
  const circle = circles[rand(0, circles.length)];
  circle.classList.toggle("animate");
}, 500);
#rocky circle {
  opacity: 0;
  transition: opacity 400ms ease;
}

#rocky circle.animate {
  opacity: 1;
}
<div id="rocky">
  <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
  <g>
    <circle cx="50" cy="50" r="10"/>
    <circle cx="100" cy="50" r="10"/>
    <circle cx="150" cy="50" r="10"/>
    
    <circle cx="50" cy="100" r="10"/>
    <circle cx="100" cy="100" r="10"/>
    <circle cx="150" cy="100" r="10"/>
    
    <circle cx="50" cy="150" r="10"/>
    <circle cx="100" cy="150" r="10"/>
    <circle cx="150" cy="150" r="10"/>
  </g>
</svg>
</div>

If you want to achieve a repeating texture, you'll have to refer to the background as a separate entity (i.e. not in the DOM). That way the browser will be able to treat it as a texture and be able to repeat it. Otherwise, you'll have to manually tile and repeat the elements within the DOM.

Consider the example below which borrows a little bit of CSS from Ian's answer so that we use CSS animations. Transitions won't work since they won't repeat:

const svg = `
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
    <style>
        @keyframes fadeInOut {
            from {
                opacity: 1;
            }
            to {
                opacity: 0.5;
            }
        }
        circle.animated {
            animation: fadeInOut 1s ease infinite alternate;
        }

        circle.animated.fast {
            animation-duration: 0.3s;
        }
        circle.animated.medium {
            animation-duration: 0.7s;
        }
        circle.animated.slow {
            animation-duration: 1.1s;
        }
    </style>
    <g>
        <circle class="animated fast" cx="0" cy="0" r="10"/>
        <circle class="animated medium" cx="50" cy="0" r="10"/>
        <circle class="animated fast" cx="100" cy="0" r="10"/>
        <circle class="animated medium" cx="0" cy="50" r="10"/>
        <circle class="animated slow" cx="50" cy="50" r="10"/>
        <circle class="animated medium" cx="100" cy="50" r="10"/>
        <circle class="animated fast" cx="0" cy="100" r="10"/>
        <circle class="animated medium" cx="50" cy="100" r="10"/>
        <circle class="animated fast" cx="100" cy="100" r="10"/>
    </g>
</svg>
`.replace(/\r?\n/g, "");

const rocky = document.getElementById("rocky");
rocky.style.background = `url('data:image/svg+xml;utf8,${svg}')`;
#rocky {
  width: 500px;
  height: 500px;
}
<div id="rocky"></div>
zero298
  • 25,467
  • 10
  • 75
  • 100
  • Can you show how to actually embed and use the SVG with `` so that it's a repeating seamless texture like in the rock example, so I can use it on multiple separate parts of the page? – Lance Jul 30 '19 at 14:36
  • I like this shine effect a lot! – Lance Jul 30 '19 at 14:38
  • @LancePollard That's a cost for embedding the SVG. It is no longer a "texture" meaning it can't be repeated. If that is the intention, I think you'll **have** to go with a mechanism that doesn't place it in the DOM. – zero298 Jul 30 '19 at 14:39
  • Not sure what you mean. – Lance Jul 30 '19 at 14:45
  • Instead of "technically" making it a texture, I would be okay with making it fit 3 or 4 versions per row of the rock, and then vertically having like 10 or 15 rows type of thing. Then perhaps you could use `` and ``, though I'm not sure if that is the right approach. – Lance Jul 30 '19 at 14:49
  • @LancePollard consider my edit that will let you use it as a `background`. The problem with using the DOM if you want to repeat is that you will end up with a *lot* of repeats. – zero298 Jul 30 '19 at 15:19