UNPKG

svg-filter-animation

Version:

Plays given SVG-filter animations.

206 lines (180 loc) 6.79 kB
const $ = require('jquery'); const tween = require('./tween-functions'); const SVGFilterAnimation = {}; SVGFilterAnimation.module = (function () { let keyframePath = ''; let filterContainerId = ''; let keyframeTracks = null; let $filter; /** * Read keyframes from JSON file * @returns {*} * @private */ function _loadJSON () { return (function() { let jsonData = null; $.ajax({ 'async': false, 'global': false, 'url': keyframePath, 'dataType': 'json', 'success': function (data) { jsonData = data; } }); return jsonData; })(); } /** * Trigger animation playback based on loaded keyframes * @private */ function _triggerAnimation () { if(keyframeTracks.length > 0) { for (let i = 0; i < keyframeTracks.length; i++) { filterContainerId = keyframeTracks[i].filterContainerId; $filter = $('#' + filterContainerId); _playTrack(keyframeTracks[i]); } } } /** * Play an animation track * @param track * @private */ function _playTrack (track) { let start = null; let firstKey = track.keyframes[0]; let lastKey = track.keyframes[track.keyframes.length - 1]; let trackDuration = lastKey.timestamp - firstKey.timestamp; let keyframes = track.keyframes; function trackLoop (timestamp) { if (!start) start = timestamp; let progress = timestamp - start; // Iterate keyframes for (let keyframeIndex = 0; keyframeIndex < keyframes.length; keyframeIndex++) { // Trigger keyframe playback based on timestamp if (keyframes[keyframeIndex].timestamp <= progress) { // Get start- and end-key pair let startKey = keyframes[keyframeIndex]; let endKey = undefined; if (keyframeIndex + 1 < keyframes.length) { endKey = keyframes[keyframeIndex + 1] } // Trigger animation playback for current pair if (startKey && endKey) { let duration = endKey.timestamp - startKey.timestamp; _playAnimation(parseInt(track.filterId), track.propertyName, track.propertyIndex, duration, startKey.propertyValue, endKey.propertyValue); keyframes.shift(); // Remove current start key from list keyframeIndex = -1; // Reset key index } } } if (progress < trackDuration) { window.requestAnimationFrame(trackLoop); } } window.requestAnimationFrame(trackLoop); } /** * Play a single animation * @param filterId * @param propertyName * @param propertyIndex * @param duration * @param startValue * @param endValue * @private */ function _playAnimation (filterId, propertyName, propertyIndex, duration, startValue, endValue) { let start = null; let $filterNode = $filter.find('#' + filterContainerId + filterId.toString()); function step (timestamp) { if (!start) start = timestamp; let progress = timestamp - start; if ($filterNode.length) { _setPropertyValue($filterNode, propertyName, propertyIndex, tween.easeInOutCubic(progress, startValue, endValue, duration)); _updateContainer(); } if (progress < duration) { window.requestAnimationFrame(step); } else { // Clean value if ($filterNode.length) { _setPropertyValue($filterNode, propertyName, propertyIndex, endValue); } } } window.requestAnimationFrame(step); } /** * Set property value at given positon to given value * @param $filterNode * @param propertyName * @param propertyIndex * @param propertyValue * @private */ function _setPropertyValue ($filterNode, propertyName, propertyIndex, propertyValue) { /** Special nodes **/ switch ($filterNode[0].tagName) { case 'feComponentTransfer': { // Extract color channel from property name let channel = propertyName.charAt(4); let $childNode = $filterNode.find('feFunc' + channel); // Extract final property attribute name const allowedPropertyNames = ['TableValues', 'Slope', 'Intercept', 'Amplitude', 'Exponent', 'Offset']; let childPropertyName = ''; for (let i = 0; i < allowedPropertyNames.length; i++) { if (propertyName.includes(allowedPropertyNames[i])) { childPropertyName = allowedPropertyNames[i]; childPropertyName = childPropertyName.charAt(0).toLowerCase() + childPropertyName.slice(1); } } $filterNode = $childNode; propertyName = childPropertyName; break; } case 'feColorMatrix': { propertyName = 'values'; break; } case 'feFlood': { propertyName = 'flood-opacity'; break; } } let properties = $filterNode.attr(propertyName).split(' '); properties[propertyIndex] = propertyValue; let outputString = properties.join(' '); $filterNode.attr(propertyName, outputString); } /** * Trigger container update * @private */ function _updateContainer () { $filter.redraw(); } $.fn.redraw = function() { return this.hide(0, function() { $(this).show(); }); }; function init (keyframeFile) { // Set keyframe file path keyframePath = keyframeFile; // Load JSON data keyframeTracks = _loadJSON(); // Start animation _triggerAnimation(); } return { init: init }; })(); exports.SVGFilterAnimation = function (keyframeFile) { SVGFilterAnimation.module.init(keyframeFile); };