Saving animated SVG from a webpage - javascript

There's an Internet Site with a Chinese dictionary that uses animations to show you the proper way to write Chinese characters. I'm not entirely sure how the animations work, but they appear to SVG-based. You can find an example of the animation here:
http://ce.linedict.com/dict.html#/cnen/entry/e1d0a1716f89470d88087dce285914a1
Be sure to click "Strokes" to view the animation.
So here"s the challenge I'm facing: I was wondering if there is any way to download the animations so that they can be viewed offline. The reason I want to do this is to add them to Anki, a flashcard app that I use on my iPad when I'm on the go (i.e. offline).
I tried examining the element in Firefox and saving the inner HTML of the element as a separate file, but that results in an static page. Doing the same in Safari is impossible as the inner HTML keeps changing as the animation is rendered, which makes me think that the inner HTML is being rendered on the fly by a separate (?) Java / (?) Jquery script nested somewhere on the site. Unfortunately, my knowledge of Java is rudimentary at best, and although I've started a tutorial online to get myself acquainted with the code, I think it'd be ages before I can figure out what's going on here. That's why I'm looking for help from you guys who have more experience with webpages than I do.
So is there any way to save the animation for offline use, either as a code or a svg / animated gif file? Anki, the flashcard app I'm using, uses HTML to render its content in a similar manner to web browsers (and as far as I know there are ways to integrate JavaScript).
I guess that I could use screen capturing programs to record the animation and save it as a .gif file, but given the fact that there are about 3500-5000 Chinese characters that a learner must master to be fluent in Chinese, that would take a hell of a time, so I'm looking for something that can be Scriptable (I have some working understanding of Apple Scripting, and could take it from myself there once I understand how to the animation works).
I'd appreciate any help or suggestion that could bring me any further.
---Edit on 2015-05-23:
Like I mentioned in a reply to Robert below, I've had a look at the DOM structure of a different entry, this time for a single character rather than a multi-character word. When the stroke order animation button on the page is activated, the following code can be found in the DOM structure:
<input type="hidden" id="hidden_strokeFileName" value="1200爸.swf">
as you can see there seems to be a reference to a file name with a flash-based animation. If the animation is indeed contained in an .swf file, it should be possible to download it for offline use, right? However, I'm baffled as to how the browser stitches up the URL under which the animation can be found so that I could download it. Can anyone help?
Here's the link to the page: http://ce.linedict.com/dict.html#/cnen/entry/cca145dd67574395a5a28af08a3afb30

I created a simple tool (script snippet, 16 lines of code): https://gist.github.com/VityaSchel/bc2a83e330661c2bae580ebb4aab05cf
It records svg changes using MutationObserver interface. I used it to record animations of "d" argument but you can modify this script to record all changes in svg structure and attributes.
One major issue with this tool is that it records unevenly, so some parts may be slightly slower than others.
let recordedChanges = []
let startRecordingSVG = () => { recordedChanges = [] }
let stopRecordingSVG = () => { console.log(JSON.stringify(recordedChanges)) }
let observer = new MutationObserver(mutationRecords => {
for(const record of mutationRecords) {
if(record.attributeName === 'd') {
recordedChanges.push(record.target.getAttribute('d'))
}
}
})
observer.observe($0, {
subtree: true,
attributes: true
})
/* USAGE */
// 1. Select target svg (or better it's parent, because svg may be overwritten) in Elements panel
// 2. Paste this into Console
// 3. Paste startRecordingSVG() into console
// 4. Do something so animation plays
// 5. Paste stopRecordingSVG() into console
// 6. An array with all animation keyframes will be printed in JSON format, use it to transform to lottie animation or CSS keyframes
In addition to that I created a function to convert this array to CSS keyframes:
function arrayFramesToCSSKeyframes(array) {
let frames = '', i = 1
for(let framePath of array) {
const percentage = i/array.length*100
const frameCode = []
frames += ` ${Math.round(percentage)}% {\n`
frames += ` d: path("${framePath}");\n`
frames += ` }\n\n`
i++
}
return `#keyframes frames {\n${frames}\n}`
}

Related

Call function without actually executing it

So I have such a weird question and I don't know if this is possible, but I will give it a shot anyways.
We have implemented OneTrust, which is a third-party cookie consent vendor and it works great and all, but we have one small hiccup that we are trying to resolve.
So within the below function:
toggleVideo: function (videoWrapper, src, cover, type, element) {
var videoElement = video.buildVideo(src, type);
// We build out video-player element
videoWrapper.html(videoElement);
// We define our variables
let onetrust = window.OneTrust;
let onetrust_obj = window.ONETRUST_MODULE;
let target = videoWrapper.html(videoElement).children('iframe');
console.log(onetrust.Close());
// Now we wait and observe for a src attribute and then show the video.
onetrust_obj.src_observer(target, function() {
video.toggle_show(videoWrapper, cover);
});
},
We have an <iframe> element that when clicked play, will wait for consent to execute - The problem is that it needs to "refresh" OneTrust so that it can change the data-src to src attribute (This is all handled using OneTrust JS, so I have no control).
When I add in the console.log(onetrust.Close());, it works just as intended and resumes playing the video when consent is given, the downfall is that it outputs an error in the console. If I remove it, the videos will not play after consent is given.
I don't want to actually execute the onetrust.Close() method as it will close the banner.
OneTrust doesn't have a proper way to "Refresh" their initialization, the techs told me that this was a one-off case, where they don't even know how to handle it.
My questions:
Is there a way that I can properly call onetrust.Close() (Seems to be the only call that actually engages the video to play after) without actually executing it?
If not, is there a way that I can somehow similarly log it, but not actually log it in the console?
Thanks all!
Strange one, may be a race-condition issue so making your code run in the next procedural iteration may resolve the issue - this can be done by adding a setTimeout with no timer value.
setTimeout(() => {
onetrust_obj.src_observer(target, function() {
video.toggle_show(videoWrapper, cover);
});
});
Alternatively, it may be worth digging into the onetrust.Close() method to see if there are any public utilities that may help 'refresh' the context you are working in.
Another idea would be to see what happens after if you ran the onetrust_obj.src_observer code block again.
EDIT: I would like to be clear that I'm just trying to help resolve debugging this, without seeing a working environment it's difficult to offer suggestions 😄

Can content be updated via Ajax to end user without loosing scroll position from update/refresh?

This question is being asked to check if it is POSSIBLE to do what I’d like it to do, not so much as a HOW to do it question (although if someone wants to pen something up is always good too). As I am more in the infancy of my web-dev learning I figured it prudent to determine if the hopeful result is even possible before investing the substantial time into it to achieve it. I have read conflicting things from various references on the web and figured this is the place to ask others' opinions to be sure one way or the other.
I’m running Drupal 8 (8.9.13 at time of writing). The page uses Views. To keep it simple the page has 3 sections; a top, middle, and bottom (this is the page/content itself, this is NOT a header, body, footer). The bottom section/View consists of multiple div’s/sections. The content of all 3 sections is primarily links.
I’m using/trying the module ‘Views Auto-Refresh D8’. After install and setup it updates the page perfectly EXCEPT that not only after a content update, but ALSO after each Ajax check/interval the page returns to the top of the page, causing the user experience to be unacceptable. In other words, if reading the bottom of the page, once the page automatically checks for updates via Ajax the page suddenly returns to top of page.
Researching this I got the impression that is normal behavior for Ajax. I also got the impression that this behavior can be overridden, however, other sources made it sound as though it can’t. Hence the purpose of this question here.
The Javascript for the ‘Views Auto-Refresh D8’ module looks like this:
(function ($, Drupal, drupalSettings) {
// START jQuery
Drupal.behaviors.views_autorefresh = {
attach: function(context, settings) {
for(var view_name in settings.views_autorefresh) {
for(var view_display in settings.views_autorefresh[view_name]) {
var interval = settings.views_autorefresh[view_name][view_display];
var execution_setting = '.view-'+view_name.replace(new RegExp('_','g'),'-')+'.view-display-id-'+view_display;
if($(context).find(execution_setting).once(execution_setting).length > 0) {
// Delete timeOut before reset it
if(settings.views_autorefresh[view_name][view_display].timer) {
clearTimeout(settings.views_autorefresh[view_name][view_display].timer);
}
settings.views_autorefresh[view_name][view_display].timer = setInterval(
function() {
Drupal.behaviors.views_autorefresh.refresh(execution_setting)
}, interval
);
}
}
}
},
refresh: function(execution_setting) {
$(execution_setting).trigger('RefreshView');
}
}
Through researching this question I found closely related questions saying the page refresh can be bypassed/prevented by adding a javascript function such as:
preventDefault() / event.preventDefault()
However I’m not entirely sure if this event method can be used to achieve the result I’m pursuing? Or if the following is a better avenue to explore?
The other possible workaround/idea I came up with and am curious about is that I use a module called ‘Geysir’ that allows me to update my content directly on the page from the front-end. The content can be moved, deleted directly on the page, or even modified within a modal overlay that then updates the content without the page refresh/scroll back to top that the ‘Views Auto-Refresh D8’ is doing. I’m wondering if the logic from the Geysir module can be applied to the Views Auto-Refresh D8 module? With my moderate javascript knowledge it looks like the relevant js/Ajax logic in the Geysir module is:
$.each(Drupal.ajax.instances, function (index, event) {
var element = $(event.element);
if (element.hasClass('geysir-paste')) {
if (href === event.element_settings.url) {
event.options.url = event.options.url.replace('/' + paragraph_id + '/', '/' + parent_id + '/');
}
}
});
});
});
return false;
});
}
};
Drupal.AjaxCommands.prototype.geysirReattachBehaviors = function() {
Drupal.ajax.instances = Drupal.ajax.instances.filter(function(el) {
return el;
});
Drupal.attachBehaviors();
};
})(jQuery, Drupal, drupalSettings);
(lines 40-64)
That’s pretty much where I’m at before moving forward. So:
Is it even possible to maintain end-users scroll/page position while the Ajax call is being made and when content is updated via Ajax?
and if so,
Which avenue above is best to pursue, OR, is there yet another avenue I haven’t yet run across yet?

Making a Chrome extension for a site that uses React. How to persist changes after re-rendering?

I want my extension to add elements to a page. But the site uses React which means every time there's a change the page is re-rendered but without the elements that I added.
I'm only passingly familiar with React from some tutorial apps I built a long time ago.
I implemented #wOxxOm's comment to use MutationObserver and it worked very well.
static placeAndObserveMutations (insertionBoxSelector) {
let placer = new Placement ();
placer.place(insertionBoxSelector);
placer.observeMutations(insertionBoxSelector);
}
place (insertionBoxSelector) {
let box = $(insertionBoxSelector)
this.insertionBox = box; //insertionBox is the element that the content
// will be appended to
this.addedBox = EnterBox.addInfo(box); //addedBox is the content
// Worth noting that at this point it's fairly empty. It'll get filled by
// async ajax calls while this is running. And all that will still be there
// when it's added back in the callback later.
}
observeMutations(insertionBoxSelector) {
let observer = new MutationObserver (this.replaceBox.bind(this));
// this.insertionBox is a jQuery object and I assume `observe` doesn't accept that
let insertionBox = document.querySelector(insertionBoxSelector);
observer.observe(title, {attributes: true});
}
replaceBox () {
this.insertionBox.append(this.addedBox);
_position (this.addedBox);
}
That being said someone suggested adding the content above the React node (i.e. the body) and just positioning the added content absolutely relative to the window. And as this will actually solve a separate problem I was having on some pages of a site I'll probably do that. Also, it's far simpler.
But I thought this was still an interesting solution to a rare problem so I wanted to post it as well.

Is there a way to speed up the canvas.toDatalessJSON() function to save the history of the canvas?

I am using fabric.js in my project and I have to render 10000+ items with lot of custom variables. I tried to optimize the performance with the info from this page https://github.com/kangax/fabric.js/wiki/Optimizing-performance but still its taking lot of time.
We have undo, redo, zoom and edit features in our project so I am using JSON.stringify(canvas.toDatalessJSON()); for storing the details. Its working completely fine until the no of objects are less than 7000.. Is there any way to simplify the JSON function?
Here is the working fiddle http://jsfiddle.net/r0zrpbqs/
function updateModifications(savehistory) {
if (savehistory === true) {
myjson = JSON.stringify(canvas.toDatalessJSON());
//console.log(myjson);
chk = chk + 1;
state.push(myjson);
}
}
Try just JSON.stringify(canvas). Presumably toDatalessJSON() is optimized for minimal output size, not speed of execution.

Event handling in Dojo

Taking Jeff Atwood's advice, I decided to use a JavaScript library for the very basic to-do list application I'm writing. I picked the Dojo toolkit, version 1.1.1. At first, all was fine: the drag-and-drop code I wrote worked first time, you can drag tasks on-screen to change their order of precedence, and each drag-and-drop operation calls an event handler that sends an AJAX call to the server to let it know that order has been changed.
Then I went to add in the email tracking functionality. Standard stuff: new incoming emails have a unique ID number attached to their subject line, all subsequent emails about that problem can be tracked by simply leaving that ID number in the subject when you reply. So, we have a list of open tasks, each with their own ID number, and each of those tasks has a time-ordered list of associated emails. I wanted the text of those emails to be available to the user as they were looking at their list of tasks, so I made each task box a Dijit "Tree" control - top level contains the task description, branches contain email dates, and a single "leaf" off of each of those branches contains the email text.
First problem: I wanted the tree view to be fully-collapsed by default. After searching Google quite extensively, I found a number of solutions, all of which seemed to be valid for previous versions of Dojo but not the one I was using. I eventually figured out that the best solution would seem to be to have a event handler called when the Tree control had loaded that simply collapsed each branch/leaf. Unfortunately, even though the Tree control had been instantiated and its "startup" event handler called, the branches and leaves still hadn't loaded (the data was still being loaded via an AJAX call). So, I modified the system so that all email text and Tree structure is added server-side. This means the whole fully-populated Tree control is available when its startup event handler is called.
So, the startup event handler fully collapses the tree. Next, I couldn't find a "proper" way to have nice formatted text for the email leaves. I can put the email text in the leaf just fine, but any HTML gets escaped out and shows up in the web page. Cue more rummaging around Dojo's documentation (tends to be out of date, with code and examples for pre-1.0 versions) and Google. I eventually came up with the solution of getting JavaScript to go and read the SPAN element that's inside each leaf node and un-escape the escaped HTML code in it's innerHTML. I figured I'd put code to do this in with the fully-collapse-the-tree code, in the Tree control's startup event handler.
However... it turns out that the SPAN element isn't actually created until the user clicks on the expando (the little "+" symbol in a tree view you click to expand a node). Okay, fair enough - I'll add the re-formatting code to the onExpand() event handler, or whatever it's called. Which doesn't seem to exist. I've searched to documentation, I've searched Google... I'm quite possibly mis-understanding Dojo's "publish/subscribe" event handling system, but I think that mainly because there doesn't seem to be any comprehensive documentation for it anywhere (like, where do I find out what events I can subscribe to?).
So, in the end, the best solution I can come up with is to add an onClick event handler (not a "Dojo" event, but a plain JavaScript event that Dojo knows nothing about) to the expando node of each Tree branch that re-formats the HTML inside the SPAN element of each leaf. Except... when that is called, the SPAN element still doesn't exist (sometimes - other times it's been cached, just to further confuse you). Therefore, I have the event handler set up a timer that periodically calls a function that checks to see if the relevant SPAN element has turned up yet before then re-formatting it.
// An event handler called whenever a "email title" tree node is expanded.
function formatTreeNode(nodeID) {
if (dijit.byId(nodeID).getChildren().length != 0) {
clearInterval(nodeUpdateIntervalID);
messageBody = dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML
if (messageBody.indexOf("<b>Message text:</b>") == -1) {
messageBody = messageBody.replace(/>/g, ">");
messageBody = messageBody.replace(/</g, "<");
messageBody = messageBody.replace(/&/g, "&");
dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML = "<b>Message text:</b><div style=\"font-family:courier\">"+messageBody+"</div>";
}
}
}
// An event handler called when a tree node has been set up - we changed the default fully-expanded to fully-collapsed.
function setupTree(theTree) {
dijit.byId("tree-"+theTree).rootNode.collapse();
messageNode = dijit.byId("tree-"+theTree).rootNode.getChildren();
for (pl = 0; pl < messageNode.length; pl++) {
messageNode[pl].collapse();
messageNode[pl].expandoNode.onclick = eval("nodeUpdateIntervalID = setInterval(\"formatTreeNode('"+messageNode[pl].id+"')\",200); formatTreeNode('"+messageNode[pl].id+"');");
}
}
The above has the feel of a truly horrible hack, and I feel sure I must have taken a wrong turn somewhere early on in my thought process. Can someone please tell me:
The correct way to go about putting nicely-formatted text inside a Dojo/Dijit Tree control.
The correct way to handle Dojo events, like where I can figure out what events are available for me to subscribe to.
A better JavaScript library to use (can I do what I want to with JQuery and avoid the all-around-the-houses approach seen above?).
PS: If you're naming a software project, give thought to its name's uniqueness in Google - I'm sure searching for "Dojo" documentation in Google would be easier without all the martial arts results getting in the way.
PPS: Firefox spellchecker knows how to spell "Atwood", correcting me when I put two 'T's instead of one. Is Jeff just that famous now?
I assume that you followed the dijit.Tree and dojo.data in Dojo 1.1 tutorial which directed you to pass the data to the tree control using a data store. That had me banging my head of a brick wall for a while.
Its not really a great approach and the alternative is not really well documented. You need to create a use model instead. I have included an example below of a tree model that I created for displaying the structure of an LDAP directory.
You will find the default implementation of the model in your dojo distribution at ./dijit/_tree/model.js. The comments should help you understand the functions supported by the model.
The IDirectoryService class the code below are stubs for server-side Java POJOs generated by Direct Web Remoting (DWR). I highly recommend DWR if you going to be doing a lot of client-server interaction.
dojo.declare("LDAPDirectoryTreeModel", [ dijit.tree.model ], {
getRoot : function(onItem) {
IDirectoryService.getRoots( function(roots) {
onItem(roots[0])
});
},
mayHaveChildren : function(item) {
return true;
},
getChildren : function(parentItem, onComplete) {
IDirectoryService.getChildrenImpl(parentItem, onComplete);
},
getIdentity : function(item) {
return item.dn;
},
getLabel : function(item) {
return item.rdn;
}
});
And here is an extract from the my JSP page where I created the model and used it to populate the tree control.
<div
dojoType="LDAPDirectoryTreeModel"
jsid="treeModel"
id="treeModel">
</div>
<div
jsid="tree"
id="tree"
dojoType="dijit.Tree" model="treeModel"
labelAttr="name"
label="${directory.host}:${directory.port}">
</div>

Categories

Resources