How do you animate an external SVG file with anime.js? - javascript

I tried including the svg file as an img element, which didn't work - anime.js didn't recognize the svg. Then put it in an object element, which also didn't work.
So how do you set up a framework to recognize the svg and the elements within it?

You do need to use an object element, but you can't view the html file directly - you need to serve it from a server, e.g. with Python - at a console do
python -m http.server 8001
then view it on http://localhost:8001
Here's an example -
<html>
<!-- note: must run this from a server for svg to load properly -->
<!-- eg `python -m http.server 8001` -->
<!-- see https://stackoverflow.com/questions/11434916/javascript-accessing-inner-dom-of-svg -->
<head>
<script src="anime.min.js"></script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background: #222;
}
object {
opacity: 0;
background: black;
border: 1px solid #aaa;
}
</style>
</head>
<body>
<script>
function loaded() {
// get references to svg and subelements
const svg = document.querySelector("#svg")
const title = [
svg.contentDocument.querySelector("#path30"),
svg.contentDocument.querySelector("#path34"),
]
const subtitle = [
svg.contentDocument.querySelector("#text54"),
]
const swooshes = [
svg.contentDocument.querySelector("#path42"),
svg.contentDocument.querySelector("#path38"),
svg.contentDocument.querySelector("#path50"),
svg.contentDocument.querySelector("#path46"),
]
// set some initial values
svg.style.opacity = 1
for (const element of title) {
element.style.opacity = 0
}
for (const element of subtitle) {
element.style.opacity = 0
}
for (const element of swooshes) {
element.style.opacity = 0
}
// animate elements
anime({
targets: svg,
delay: 0,
duration: 2000,
background: '#ffffff',
easing: 'linear'
})
anime({
targets: title,
opacity: 1,
delay: 0,
duration: 2000,
easing: 'linear',
})
anime({
targets: subtitle,
opacity: 0.9,
delay: 2000,
duration: 2000,
easing: 'linear',
})
let startTime = 3000
for (let i = 0; i < 4; i++) {
window.setTimeout(() => {
anime({
targets: swooshes[i],
opacity: 1,
duration: 2000,
easing: 'linear',
direction: 'alternate',
loop: true,
})
}, startTime)
startTime += 500
}
}
</script>
<!-- must put svg in an object element -->
<!-- https://stackoverflow.com/questions/28652648/how-to-use-external-svg-in-html -->
<object data="Emergent_Logo.svg" type="image/svg+xml" id="svg" onload="loaded()"></object>
</body>
</html>

animejs requires the raw svg code instead of accessing it via iframe/img tag.
To get the raw code, simply open the .svg file in one of the editors and copy the code from it to directly in your html file.
It should now recognize the code and animate/morph the svg however you want.

Related

Why can't I modify the property js animate() applies to an element?

After the animation finishes, the click event will no longer work, in fact, changing the value from the dev tools will not work either. Using fill: 'none' will work, but I want to use the styling applied by the animation.
Is this expected behaviour ? The animation finishes and applies the style to the element as an inline style (or does it?), why can't it be modified in this case ? Is there a way to keep the style that the animation applies and then modify it?
Example: event listener no longer works after animation finished
let square = document.querySelector('.circle')
square.addEventListener('click', () => {
square.style.transform = 'translateY(50px)'
})
let animation = [
{transform: 'translateY(100px)'}
]
square.animate(animation, {duration: 2000, fill: 'both'})
.circle {
width: 100px;
height: 100px;
background-color: #c965a6;
border-radius: 50%;
}
<div class='circle'></div>
Edit: Found a solution
Use fill: 'none', listen for when the animation finishes and manually apply the final styling to the element.
let anim = square.animate(animation, {duration: 2000, fill: 'none'})
anim.addEventListener('finish', () => {
square.style.transform = 'translateX(0px)'
})
Though this seems more like an workaround, I'd still like to know why you can't modify properties applied by an animation
This is simply how the Web Animations API (WAAPI) is intended to function. Styles applied by the API take precedence over all other css styles, even inline styles, unless important is applied.
See the section "Combining effects" of the specs.
(The API does not, by the way, change the style attribute, but applies the styles in the WAAPI context. You can get the styles applied by the Animations API by callingel.getAnimations().)
There are two ways you can apply styles after a WAAPI style is applied:
Use important, like this:
square.style.setProperty( 'transform', 'translateY(0px)', 'important' );
Cancel the whole animation and then apply a style: anim.cancel();
let square = document.querySelector('.circle');
let anim;
square.style.transform = 'translateY(50px)';
square.addEventListener('click', () => {
anim.cancel();
square.style.transform = 'translateY(0px)';
});
let animation = [
{transform: 'translateY(100px)'}
];
anim = square.animate(animation, {duration: 2000, fill: 'both'});
.circle {
width: 100px;
height: 100px;
background-color: #c965a6;
border-radius: 50%;
}
<div class='circle' ></div>
You can cancel() your Animation, so that its effects are removed.
// Beware I renamed your variables so they match better (except for the triangle of course :P)
const square = document.querySelector('.circle')
const keyframes = [
{transform: 'translateY(100px)'}
]
const animation = square.animate(keyframes, {duration: 2000, fill: 'both'})
square.addEventListener('click', () => {
animation.cancel();
square.style.transform = 'translateY(50px)'
})
.circle {
width: 100px;
height: 100px;
background-color: #c965a6;
border-radius: 50%;
}
<div class='circle'></div>

Why is the loop property not working on my second animation?

I'm using the anime JS library, I've added two animations to a div, the second animation starts when the first animation has finished.
But the problem is that I want just the second animation to keep looping, I've set the property "loop" to "true" on the second animation, but it still won't loop, every property works fine except the "loop" property, how can I make just the second animation loop over and over again?
HTML code:
<body>
<div></div>
</body>
CSS code:
div{
height: 100px;
width: 100px;
background-color: rgb(161, 6, 6);
}
JavaScript code:
"use strict";
let square = document.querySelector("div")
anime.timeline()
.add({
targets: square,
backgroundColor: "#3dbf7a",
duration: 2000,
})
.add({
targets: square,
translateX: [0,100],
translateY: [0,100],
duration: 1000,
loop: true,
})
As mentioned in the comments, this is an existing issue in the library. In this workaround, I needed to put the anim.restart() call inside a setTimeout to ensure the javascript executed in a new event loop.
let square = document.querySelector("div");
anime.timeline()
.add({
targets: square,
backgroundColor: "#3dbf7a",
duration: 2000,
}).add({
targets: square,
translateX: [0, 100],
translateY: [0, 100],
duration: 1000,
changeComplete: anim => setTimeout(() => anim.restart())
});
div {
height: 100px;
width: 100px;
background-color: rgb(161, 6, 6);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<div></div>
jsFiddle

How to delete image from array if display="none" using Vue.js?

I am using Vue.js in my project. I have a background of images which are animated, they are moving from up to down. Everything connected to the random image, position and etc is inside created():
const vue = require("#/assets/images/vue.png");
const bootstrap = require("#/assets/images/bootstrap.png");
const bulma = require("#/assets/images/bulma.png");
export default {
name: "randImg",
data() {
return {
images: [
vue,
bootstrap,
bulma
],
addedImage: [],
imgTop: -100,
imgLeft: -100,
imgHeight: 64,
imgWidth: 64,
changeInterval: 250
}
},
created() {
const randomImg = func => setInterval(func, this.changeInterval);
randomImg(this.randomImage);
randomImg(this.addImage);
randomImg(this.randomPosition);
},
mounted: function () {
if (this.addedImage[i] = {
style: {display: `none`}
}) {
this.addedImage.remove(this.addedImage[i]);
}
},
methods: {
randomImage() {
const idx = Math.floor(Math.random() * this.images.length);
this.selectedImage = this.images[idx];
},
randomPosition() {
const randomPos = twoSizes => Math.round(Math.random() * twoSizes);
this.imgTop = randomPos(screen.height / 10 - this.imgHeight);
this.imgLeft = randomPos(screen.width - this.imgWidth);
},
addImage() {
if (this.addedImage.length > 500) {
this.addedImage.splice(0, 300);
} else {
this.addedImage.push({
style: {
top: `${this.imgTop}px`,
left: `${this.imgLeft}px`,
height: `${this.imgHeight}px`,
width: `${this.imgWidth}px`
},
src: this.selectedImage
});
}
}
}
}
css
.image {
position: fixed;
z-index: -1;
opacity: 0;
animation-name: animationDrop;
animation-duration: 5s;
animation-timing-function: linear;
animation-iteration-count: 1;
filter: blur(3px);
will-change: transform;
}
#keyframes animationDrop {
15% {
opacity: 0.2;
}
50% {
opacity: 0.4;
}
80% {
opacity: 0.3;
}
100% {
top: 100%;
display: none;
}
}
and html
<div class="randImg">
<img class="image" :style="image.style"
:src="image.src"
v-for="image in addedImage">
</div>
My site is lagging because images are adding to the DOM infinitely. The idea of my animation is that when my image is on keyframe 100% it will have display none. So I decide to simply create an if statement inside mounted() but it doesn't work; I get the error message "ReferenceError: i is not defined".
How can I delete the images when their display become none?
You want each image to persist for five seconds (based on your animation-duration), and you're adding one image every 250ms (based on your changeInterval variable). This means that your image array needs to contain a maximum of twenty images, rather than the 500 you're currently limiting it to.
You could control this by modifying your addImage function to drop the oldest image before adding the newest one. (You're already doing this, except that you're waiting until five hundred of them have built up and then splicing off three hundred at once; better to do it one at a time:)
addImage() {
if (this.addedImage.length > 20) {
this.addedImage.shift() // remove oldest image (the first one in the array)
}
// add a new image to the end of the array:
this.addedImage.push({
style: {
top: `${this.imgTop}px`,
left: `${this.imgLeft}px`,
height: `${this.imgHeight}px`,
width: `${this.imgWidth}px`
},
src: this.selectedImage
});
}
There's no need to read the display value from the DOM, you can just depend on the timing to ensure that the images aren't removed before they're supposed to; modifying the array is all that's necessary to remove the elements from the DOM. (You could harmlessly leave a few extra in the array length, just in case, but there's no need to go all the way to 500.) mounted() wouldn't be useful for this case because that function only runs once, when the component is first drawn onto the page, before there's anything in the addedImages array.
Using v-if to remove images which style is display: none. And i think you can
delete all code in mounted().
<div class="randImg">
<template v-for="image in addedImage">
<img v-if="image.style.display !== 'none'" class="image"
:style="image.style" :src="image.src">
</template>
</div>

jQuery Transit Function Complete

I have created these two functions using jquery transit that are designed to slide in a new block of html on to the page when the user presses on the right or left buttons. They work some of the time. Other times the content will load and then not unhide itself. So it will be loaded and you can inspect it with chrome and see the display is still set to none. Other times there are no problems and it works exactly as intended. There are no errors on page. Really the only JavaScript on the page is several different versions of this function that all load a different page and scroll it in, in some matter or form.
My question is: Am I using the function completes correctly from jquery Transit? Is my function working as intended or have I missed something big and there not set up properly.
function ShowPartTiles_FromLeft() {
$("#SeriesPartBrand").transition({
opacity: 0,
x: "1000px"
}, 500, "out", function() {
$("#SeriesPartBrand").transition({
opacity: 0,
x: "-1000px"
}, 0, "out")
}), $("#SeriesPartBrand").load("<?php echo uri_string() ?>/part", function() {
$("#SeriesPartBrand").transition({
opacity: 1,
x: "0"
}, 500, "in")
})
}
function ShowPartTiles_FromRight() {
$("#SeriesPartBrand").transition({
opacity: 0,
x: "-1000px"
}, 500, "out", function() {
$("#SeriesPartBrand").transition({
opacity: 0,
x: "1000px"
}, 0, "out")
}), $("#SeriesPartBrand").load("<?php echo uri_string() ?>/part", function() {
$("#SeriesPartBrand").transition({
opacity: 1,
x: "0"
}, 500, "in")
})
}
There is no transition function in jQuery, there is an animate one.
But I would advise using css3 transition property:
#SeriesPartBrand {
transition: all 0.5s;
}
function ShowPartTiles_FromLeft() {
$("#SeriesPartBrand").css({opacity: 0, left: 1000});
$("#SeriesPartBrand").off('transitionend').on('transitionend', function() {
$("#SeriesPartBrand").css({opacity: 0, left: -1000});
$("#SeriesPartBrand").load("<?php echo uri_string() ?>/part", function() {
$("#SeriesPartBrand").css({opacity: 0, left: 0});
});
});
}
This should get you started. Also don't name your JS functions with first capital letter and don't, the name would look better like this: "showPartTilesFromLeft()".
And html ids and classes must be all lowercase with words separated by comma "-".

Javascript degradation solutions for CSS3 animations

I've been creating a number of small thick client JavaScript apps for an iPad app, which loads the relevant app in a UIWebview. I am now making them cross browser and need to incorporate some fallbacks for CSS animations and transitions using JavaScript.
My webkit specific implementation uses CSS classes for all animations/transitions, for which the start and end states are known at design time, using add/remove class in javascript and utilising the relevant webkitTransitionEnd/webkitAnimationEnd events.
For 'dynamic' transitions I have a simple 'animate' function which just applies styling properties to relevant elements.
I would like to keep the internal API for appling transitions as similar as possible to the current implementation by simple adding/removing classes etc. I generally have a CSS and js file (both minified) for an app.
So a few questions/points that I would appreciate any input on:
IE7/8 issues - IE9.js
Dynamically adding vendor specific prefixes - so far found 'jquery.css3finalize'.
Transitioning to a class: 'jquery.animateToClass' - seems to search stylesheet(s) every time a class is applied - should relevant classes be cached on further lookups? Is this slow/resource hungry?
For '#keyframe' animations: I'd like to have a javascript object 'representation' of keyframes of each CSS3 animation. Therefore passing a class to the 'doAnimationWithClass' function would use normal css3 animation if available in the browser but if not it would pass the 'object version' to a function that would chain the each key frame transition using css3 transitions (if available) or jQuery.animate (or equivalent), ultimately arriving at the same result.
So for instance:
CSS:
#-webkit-keyframes an-animation {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
.an-animation {
-webkit-animation-name: an-animation;
-webkit-animation-duration: 1s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: 2;
}
JS:
var animations = {
an-animation: {
keyframes: [
{
duration: '',
timing: 'linear',
props: {
opacity: 0
}
},
{
duration: '0.5s',
timing: 'linear',
props: {
opacity: 1
}
},
{
duration: '0.5s',
timing: 'linear',
props: {
opacity: 0
}
}
]
}
};
var animationClasses = [
an-animation: {
name: 'an-animation';
duration: '1s';
timing: 'linear';
count: 2;
}
];
function doAnimationWithClass(className) {
if (modernizer.cssanimations) {
//normal implementation
}
else {
//lookup class in animationclasses
//lookup relevant animation object in animationHash
//pass to animation chaining function
}
}
The creation of animationHash etc could be done client side, or simply created at design time (with a batch/build file).
Any thoughts or pointers to a library that already does this in a more sensible way would be appreciated.
Yes, that's right. You need to transfer keyframes setting to js object. I think css animation and keyframe is a better way to write animation. JS animation way is hardly to maintain. Here is my workaround solution. And I also write a small tool for translating css keyframes to js object(css keyframes to js object) .
var myFrame = {
'0%': {
left: 0,
top: 0
},
'25%': {
left: 100,
top: 100
},
'50%': {
left: 0,
top: 300
},
'100%': {
left: 0,
top: 0
}
};
var keyframes = {
set: function($el, frames, duration) {
var animate;
animate = function() {
var spendTime;
spendTime = 0;
$.each(frames, function(idx, val) {
var stepDuration, stepPercentage;
stepPercentage = idx.replace('%', '') / 100;
stepDuration = duration * stepPercentage - spendTime;
$el.animate(val, stepDuration);
return spendTime += stepDuration;
});
return setTimeout(animate, duration);
};
return animate();
}
};
keyframes.set($('#mydiv'), myFrame, 2000);
demo: http://jsbin.com/uzodut/4/watch
Here are some important pointers http://addyosmani.com/blog/css3transitions-jquery/
Blackbingo, you answer served to me perfectly. I added the easing option, to implement a jquery fallback for ie8 in a parallax starfield (see CSS parallax background of stars) like this:
var animations = {
'STAR-MOVE': {
'0%': {
'background-position-x': '5%',
'background-position-y': '5%'
},
'100%': {
'background-position-x': '1300%',
'background-position-y': '600%'
}
}
};
var keyframes = {
set: function($el, frames, duration, easing) {
var animate;
animate = function() {
var spendTime;
spendTime = 0;
$.each(frames, function(idx, val) {
var stepDuration, stepPercentage;
stepPercentage = idx.replace('%', '') / 100;
stepDuration = duration * stepPercentage - spendTime;
$el.animate(val, stepDuration, easing);
return spendTime += stepDuration;
});
return setTimeout(animate, duration);
};
return animate();
}
};
keyframes.set($('.midground'), animations['STAR-MOVE'], 150000,'linear');
keyframes.set($('.foreground'), animations['STAR-MOVE'], 100000,'linear');

Categories