Skip to content

Instantly share code, notes, and snippets.

@bollwyvl
Last active August 7, 2023 08:33
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save bollwyvl/fe1d2806449487cdf88a to your computer and use it in GitHub Desktop.
Save bollwyvl/fe1d2806449487cdf88a to your computer and use it in GitHub Desktop.
RevealJS SVG fragment presenter

SVG fragment builds for reveal.js

Basic use case

  • make an SVG (maybe in inkscape)
    • save it someplace reveal.js can find it (maybe next to your presentation)
    • figure out how to identify them (maybe use named layers)
  • in reveal.js/index.html
    • add reveal-svg-fragment.js as a dependency
    • in a <section> of reveal.js markup
      • add data-svg-fragment="<url of the someplace>" to something, e.g. a div
      • add some things with class="fragment" inside that thing
        • add title="<a selector>" to those things
          • [*|label=<a label>] is good
          • for more about selectors, check out the W3C page

Example

Let's assume I made an SVG in Inkscape, and saved it next to my index.html. It has three layers: base, fragment1 and fragment2.

<html>
  ...
  <section>
    <div data-svg-fragment="figure.svg#[*|label=base]">
      <a class="fragment" href="[*|label=fragment1]"></a>
      <a class="fragment" href="[*|label=fragment2]"></a>
    </div>
  </section>
  ...
  <script>
    ...
    Reveal.initialize({
      dependencies: [
        ...
        {
          // maybe you put this in `plugins`
          src: 'reveal-svg-fragment.js',
          condition: function(){
            return !!document.querySelector( '[data-svg-fragment]' );
          }
          // Additional options
          // defaults to using already-loaded version, or CDN
          //d3: "./d3.min.js",
          // use a different attribute for your fragment selector
          //selector: "title",
        }
        ...
      ]
    ...
    }
    ...
  </script>
  ...
</html>

LIMITATIONS

  • won't work in Chrome against file://
    • workarounds:
      • dropbox
      • sharepoint
      • confluence
      • gist
      • python -m SimpleHTTPServer
  • probably won't work in IE
    • wontfix
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.2 -1 379 334" width="379" height="334">
<path id="puddle" fill="#9CDAF1" d="m296.94 295.43c0 20.533-47.56 37.176-106.22 37.176-58.67 0-106.23-16.643-106.23-37.176s47.558-37.18 106.23-37.18c58.66 0 106.22 16.65 106.22 37.18z"/>
<g id="shadow-legs" fill="#7DBBE6">
<path d="m161.85 331.22v-26.5c0-3.422-.619-6.284-1.653-8.701 6.853 5.322 7.316 18.695 7.316 18.695v17.004c6.166.481 12.534.773 19.053.861l-.172-16.92c-.944-23.13-20.769-25.961-20.769-25.961-7.245-1.645-7.137 1.991-6.409 4.34-7.108-12.122-26.158-10.556-26.158-10.556-6.611 2.357-.475 6.607-.475 6.607 10.387 3.775 11.33 15.105 11.33 15.105v23.622c5.72.98 11.71 1.79 17.94 2.4z"/>
<path d="m245.4 283.48s-19.053-1.566-26.16 10.559c.728-2.35.839-5.989-6.408-4.343 0 0-19.824 2.832-20.768 25.961l-.174 16.946c6.509-.025 12.876-.254 19.054-.671v-17.219s.465-13.373 7.316-18.695c-1.034 2.417-1.653 5.278-1.653 8.701v26.775c6.214-.544 12.211-1.279 17.937-2.188v-24.113s.944-11.33 11.33-15.105c0-.01 6.13-4.26-.48-6.62z"/>
</g>
<path id="cat" d="m378.18 141.32l.28-1.389c-31.162-6.231-63.141-6.294-82.487-5.49 3.178-11.451 4.134-24.627 4.134-39.32 0-21.073-7.917-37.931-20.77-50.759 2.246-7.25 5.246-23.351-2.996-43.963 0 0-14.541-4.617-47.431 17.396-12.884-3.22-26.596-4.81-40.328-4.81-15.109 0-30.376 1.924-44.615 5.83-33.94-23.154-48.923-18.411-48.923-18.411-9.78 24.457-3.733 42.566-1.896 47.063-11.495 12.406-18.513 28.243-18.513 47.659 0 14.658 1.669 27.808 5.745 39.237-19.511-.71-50.323-.437-80.373 5.572l.276 1.389c30.231-6.046 61.237-6.256 80.629-5.522.898 2.366 1.899 4.661 3.021 6.879-19.177.618-51.922 3.062-83.303 11.915l.387 1.36c31.629-8.918 64.658-11.301 83.649-11.882 11.458 21.358 34.048 35.152 74.236 39.484-5.704 3.833-11.523 10.349-13.881 21.374-7.773 3.718-32.379 12.793-47.142-12.599 0 0-8.264-15.109-24.082-16.292 0 0-15.344-.235-1.059 9.562 0 0 10.267 4.838 17.351 23.019 0 0 9.241 31.01 53.835 21.061v32.032s-.943 11.33-11.33 15.105c0 0-6.137 4.249.475 6.606 0 0 28.792 2.361 28.792-21.238v-34.929s-1.142-13.852 5.663-18.667v57.371s-.47 13.688-7.551 18.881c0 0-4.723 8.494 5.663 6.137 0 0 19.824-2.832 20.769-25.961l.449-58.06h4.765l.453 58.06c.943 23.129 20.768 25.961 20.768 25.961 10.383 2.357 5.663-6.137 5.663-6.137-7.08-5.193-7.551-18.881-7.551-18.881v-56.876c6.801 5.296 5.663 18.171 5.663 18.171v34.929c0 23.6 28.793 21.238 28.793 21.238 6.606-2.357.474-6.606.474-6.606-10.386-3.775-11.33-15.105-11.33-15.105v-45.786c0-17.854-7.518-27.309-14.87-32.3 42.859-4.25 63.426-18.089 72.903-39.591 18.773.516 52.557 2.803 84.873 11.919l.384-1.36c-32.131-9.063-65.692-11.408-84.655-11.96.898-2.172 1.682-4.431 2.378-6.755 19.25-.80 51.38-.79 82.66 5.46z"/>
<path id="face" fill="#F4CBB2" d="m258.19 94.132c9.231 8.363 14.631 18.462 14.631 29.343 0 50.804-37.872 52.181-84.585 52.181-46.721 0-84.589-7.035-84.589-52.181 0-10.809 5.324-20.845 14.441-29.174 15.208-13.881 40.946-6.531 70.147-6.531 29.07-.004 54.72-7.429 69.95 6.357z"/>
<path id="eyes" fill="#FFF" d="m160.1 126.06 c0 13.994-7.88 25.336-17.6 25.336-9.72 0-17.6-11.342-17.6-25.336 0-13.992 7.88-25.33 17.6-25.33 9.72.01 17.6 11.34 17.6 25.33z m94.43 0 c0 13.994-7.88 25.336-17.6 25.336-9.72 0-17.6-11.342-17.6-25.336 0-13.992 7.88-25.33 17.6-25.33 9.72.01 17.6 11.34 17.6 25.33z"/>
<g fill="#AD5C51">
<path id="pupils" d="m154.46 126.38 c0 9.328-5.26 16.887-11.734 16.887s-11.733-7.559-11.733-16.887c0-9.331 5.255-16.894 11.733-16.894 6.47 0 11.73 7.56 11.73 16.89z m94.42 0 c0 9.328-5.26 16.887-11.734 16.887s-11.733-7.559-11.733-16.887c0-9.331 5.255-16.894 11.733-16.894 6.47 0 11.73 7.56 11.73 16.89z"/>
<circle id="nose" cx="188.5" cy="148.56" r="4.401"/>
<path id="mouth" d="m178.23 159.69c-.26-.738.128-1.545.861-1.805.737-.26 1.546.128 1.805.861 1.134 3.198 4.167 5.346 7.551 5.346s6.417-2.147 7.551-5.346c.26-.738 1.067-1.121 1.805-.861s1.121 1.067.862 1.805c-1.529 4.324-5.639 7.229-10.218 7.229s-8.68-2.89-10.21-7.22z"/>
</g>
<path id="octo" fill="#C3E4D8" d="m80.641 179.82 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m8.5 4.72 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m5.193 6.14 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m4.72 7.08 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m5.188 6.61 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m7.09 5.66 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m9.91 3.78 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m9.87 0 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z m10.01 -1.64 c0 1.174-1.376 2.122-3.07 2.122-1.693 0-3.07-.948-3.07-2.122 0-1.175 1.377-2.127 3.07-2.127 1.694 0 3.07.95 3.07 2.13z"/>
<path id="drop" fill="#9CDAF1" d="m69.369 186.12l-3.066 10.683s-.8 3.861 2.84 4.546c3.8-.074 3.486-3.627 3.223-4.781z"/>
</svg>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>reveal.js svg fragments</title>
<meta name="description" content="An SVG Plugin for reveal.js">
<meta name="author" content="Nicholas Bollweg">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/latest/css/reveal.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/latest/css/theme/default.css" id="theme">
<!-- For syntax highlighting -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/latest/lib/css/zenburn.css">
<!-- If the query includes 'print-pdf', include the PDF print sheet -->
<script>
if( window.location.search.match( /print-pdf/gi ) ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '//cdn.jsdelivr.net/reveal.js/latest/css/print/pdf.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
</script>
<style>
.column{ width: 50%; float:left;}
</style>
<!--[if lt IE 9]>
<script src="lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section>
<h1>Reveal.js SVG Fragments</h1>
<h3>
<div class="column">
Built with <a href="http://d3js.org">d3</a>
<div data-svg-fragment="d3.svg"></div>
</div>
<div class="column">
Works with <a href="http://inkscape.org">Inkscape</a>
<div data-svg-fragment="inkscape.svg" href="http://inkscape.org"></div>
</div>
</h3>
<p>
<small>Created by <a href="http://github.com/bollwyvl">@bollwyvl</a></small>
</p>
</section>
<section>
<h1>Basic HTML Test</h1>
<div data-svg-fragment="test.svg#[*|label=base]">
<a class="fragment" title="[*|label=fragment1]"></a>
<a class="fragment" title="[*|label=fragment2]"></a>
</div>
</section>
<section data-markdown>
<script type="text/template">
# Using Markdown
<div data-svg-fragment="test.svg#[*|label=fragment1]">
<a class="fragment" title="[*|label=base]"></a>
<a class="fragment" title="[*|label=fragment2]"></a>
</div>
</script>
</section>
<section>
<h1>Questions / Feedback</h1>
<h3>
Comment/fork on
<a href="https://gist.github.com/bollwyvl/fe1d2806449487cdf88a">
GitHub
</a>
<div data-svg-fragment="github.svg"></div>
</h3>
</section>
</div>
</div>
<script src="//cdn.jsdelivr.net/reveal.js/latest/lib/js/head.min.js"></script>
<script src="//cdn.jsdelivr.net/reveal.js/latest/js/reveal.min.js"></script>
<script>
var cdn = "//cdn.jsdelivr.net/reveal.js/latest/";
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
// Parallax scrolling
// parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
// parallaxBackgroundSize: '2100px 900px',
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: cdn + 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: cdn + 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: cdn + 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: cdn + 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: 'reveal-svg-fragment.js', condition: function() { return !!document.querySelector( '[data-svg-fragment]' ); } }
]
});
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
/*
Copyright (C) 2014 Nicholas Bollweg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
;Reveal.SvgFragment = (function(Reveal){
"use strict";
var window = this,
document = window.document,
proto = window.location.protocol,
local = proto === "file:",
d3 = window.d3,
defaults = {
d3: (local ? "http:" : proto) + "//cdn.jsdelivr.net/d3js/latest/d3.min.js",
selector: "title"
};
// the main function, to be called when d3 is available
function api(){
d3 = d3 || window.d3;
var container = d3.selectAll("[data-svg-fragment]"),
slides = d3.select(".slides");
container.data(function(){
return container[0].map(function(d){
var $ = d3.select(d);
return {
container: $,
url: $.attr("data-svg-fragment")
};
});
});
container.append("iframe")
.attr({
src: function(item){ return item.url; },
// TODO: make this an option?
scrolling: "no"
})
.on("load", api.iframed);
Reveal.addEventListener("fragmentshown", api.listen(container, true));
Reveal.addEventListener("fragmenthidden", api.listen(container));
return api;
};
// generate listeners for reveal events
api.listen = function(container, show){
return function(event){
var fragment = d3.select(event.fragment);
container.filter(function(){
return this === event.fragment.parentNode;
}).each(function(item){
api.toggle(fragment, item, show);
});
return api;
};
};
// toggle a fragment
// TODO: add hide
api.toggle = function(fragment, item, show){
if(!item.svg){ return; }
var selector = fragment.attr(api.cfg("selector"));
item.svg.selectAll(selector)
.transition()
.style({opacity: show ? 1 : 0});
return api;
};
// the iframe was created for this item
api.iframed = function(item){
item.iframe = d3.select(this);
item.idoc = d3.select(this.contentDocument);
item.svg = item.idoc.select("svg");
item.dims = {
width: item.svg.attr("width") || 100,
height: item.svg.attr("height") || 100
};
item.iframe.attr(item.dims);
// see https://groups.google.com/forum/#!topic/d3-js/mTBxTLi0q1o
item.svg.attr({
width: "100%",
height: "100%",
viewBox: "0 0 "+ item.dims.width + " " + item.dims.height
});
return api.clean(item);
};
// prepare
// TODO: smarter initialization?
api.clean = function(item){
var base;
item.container.selectAll(".fragment").each(function(){
item.svg.selectAll(d3.select(this).attr(api.cfg("selector")))
.style({opacity: 0});
});
if(base = item.url.match(/(?:#)(.*)$/)){
item.svg.selectAll(base[1])
.style({opacity: 1});
}
return api;
};
// preflight, call immediately it d3 is available, otherwise load the script
api.init = function(){
var options = Reveal.getConfig().svgFragment || {};
if(window.d3){
api();
}else if(window.require){
require([api.cfg("d3")], function(_d3){
d3 = _d3;
api();
});
}else{
api.load(api.cfg("d3"), api);
}
return api;
};
// get configuration values (or defaults)
api.cfg = function(opt){
var cfg = Reveal.getConfig().svgFragment || {};
return cfg.hasOwnProperty(opt) ? cfg[opt] :
defaults.hasOwnProperty(opt) ? defaults[opt] :
function(){ throw new Error("Unknown property: "+ opt); };
};
// load a script, jacked from search, i think
api.load = function(url, callback){
var head = document.querySelector('head'),
script = document.createElement('script');
// Wrapper for callback to make sure it only fires once
var finish = function(){
if(typeof callback === 'function') {
callback.call();
callback = null;
}
};
// IE
script.onreadystatechange = function() {
if (this.readyState === 'loaded') {
finish();
}
};
script.type = 'text/javascript';
script.src = url;
script.onload = finish;
// Normal browsers
head.appendChild(script);
return api;
};
return api.init();
}).call(this, Reveal);
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Copy link

ghost commented Feb 5, 2016

Hi,
Noob here.
But I wanted to use the plugin as I would more or less need it, but I don't get it to work...

I downloaded the latest release of reveal.js form the github.

I downloaded your plugin under /plugin.

I added a line to my dependencies:
// Optional reveal.js plugins
dependencies: [
{ src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'plugin/zoom-js/zoom.js', async: true },
{ src: 'plugin/notes/notes.js', async: true },
{ src: 'plugin/reveal-svg-fragment.js', condition: function() { return !!document.querySelector( '[data-svg-fragment]' ); } }
]

I added this to insert the figure:

( made in inkscape)

proper svg

The coloured fields are different layers.

but i get this in my presentation:

result

Someting or the other has to do with the viewbox but I don't know how to implement/correct that...
As that is my hunch, I've added the top of the svg.
Any help?



<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="187.88978"
inkscape:cy="303.82656"
inkscape:document-units="px"
inkscape:current-layer="layer4"
showgrid="false"
fit-margin-top="10"
fit-margin-right="10"
fit-margin-bottom="10"
fit-margin-left="10"
inkscape:window-width="1651"
inkscape:window-height="1032"
inkscape:window-x="1949"
inkscape:window-y="18"
inkscape:window-maximized="1" />

rdf:RDF
<cc:Work
rdf:about="">
dc:formatimage/svg+xml/dc:format
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
dc:title/dc:title
/cc:Work
/rdf:RDF

@zouter
Copy link

zouter commented Feb 16, 2016

This is fixed for me if I go to document properties (in inkscape) and select as units "px".

Copy link

ghost commented Feb 25, 2016

image

still nothing :-(

Copy link

ghost commented Feb 28, 2016

just today, i saw that everything works fine in mozilla firefox, but it doesn't in chrome... Maybe that can heblp to debug?

ubuntu 15.10
firefox44.02
chrome 48.0.2564.116

@csachs
Copy link

csachs commented Feb 13, 2017

The link to the CDN hosted D3 file now leads to incompatible 4.xx … one can change it e.g. to https://cdn.jsdelivr.net/d3js/3.5.17/d3.min.js to fix it.
Could this gist probably become a github repository and be packaged?

@mvala
Copy link

mvala commented Sep 28, 2017

+1

@ebousse
Copy link

ebousse commented Feb 6, 2018

+1

@kris71
Copy link

kris71 commented May 15, 2018

It seems that the fade-out fragment type is not working. It behaves like the fade-in type.
Does anyone know how to fix that?

@aaronjg
Copy link

aaronjg commented Nov 2, 2018

I am getting an error trying to run the demo:

TypeError: container[0] is undefined[Learn More] reveal-svg-fragment.js:42:7

Firefox 62.0.3 (64-bit)

Any idea what is going on?

@deisi
Copy link

deisi commented Nov 5, 2018

@aaronjg I see the same. Was just about to post a question.

I opened an issue in the github repo here

@deisi
Copy link

deisi commented Nov 5, 2018

Okay, don't use this gist version. In the repo the bug is fixed.

Copy link

ghost commented Apr 5, 2019

Hi,

Are you able to advise how I can set me CSS animation to start when the slide is .present rather than it loading animations beforehand.

I have inserted my animations like this: <img data-src="pres-assets/svg/clip.svg"/> ---> with my CSS animations embedded inline the SVG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment