Replace image in reveal.js - reveal.js

Suppose I have two very similar images I want to display in succession (say first is a photo and the second one the same photo with some area highlighted). I'd like to avoid a transition animation and just have the second image replace the first image. Is this possible with reveal.js?
Setting data-transition="none" doesn't work very well because the previous slide still retains its animation.

Reveal.js does not have any direct method by itself for doing of what is being asked. However, it can be done using a div container and the fragment feature of reveal.js. The container includes the images, which are absolutely positioned within it, and because of that it has to have fix dimensions.
The following piece of html displays the first image and on the next slide the second image on top of the first one.
<section>
<h1>Slide 1</h1>
<div style="position:relative; width:640px; height:480px; margin:0 auto;">
<img class="fragment" width="640" height="480" src="img1.jpg" style="position:absolute;top:0;left:0;" />
<img class="fragment" width="640" height="480" src="img2.jpg" style="position:absolute;top:0;left:0;" />
</div>
</section>
While this piece of html displays the first image and on the next slide the first image fades out and the second image fades in instead.
<section>
<h1>Slide 2</h1>
<div style="position:relative; width:640px; height:480px; margin:0 auto;">
<img class="fragment fade-out" data-fragment-index="0" width="640" height="480" src="img1.jpg" style="position:absolute;top:0;left:0;" />
<img class="fragment fade-in" data-fragment-index="0" width="640" height="480" src="img2.jpg" style="position:absolute;top:0;left:0;" />
</div>
</section>

reveal.js 4.0 introduces a new r-stack class that makes it easier to stack elements on top of each other.
<div class="r-stack">
<img class="fragment" src="https://placekitten.com/450/300" width="450" height="300">
<img class="fragment" src="https://placekitten.com/300/450" width="300" height="450">
<img class="fragment" src="https://placekitten.com/400/400" width="400" height="400">
</div>
The above example fades in images on top of each other. If you want to only show one image at a time you'll need to use slightly different fragment settings.
<div class="r-stack">
<img class="fragment current-visible" src="...">
<img class="fragment current-visible" src="...">
<img class="fragment" src="...">
</div>
You can see examples of how this works in the reveal.js docs https://revealjs.com/layout/#stack

Another possible solution would be to use Javascript. With this code you can not only replace images, but any content.
It is important to apply the classes content-target and content-source. It is also important to use the display: none; style.
The html could look like:
<section>
<h3>Headline</h3>
<div class="content-target"></div>
<div style="display: none;">
<div class="content-source fragment" data-fragment-index="0">
<img src="path/to/image.jpg" />
<p>Some text</p>
</div>
<div class="content-source fragment" data-fragment-index="1">
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
</div>
<div class="content-source fragment" data-fragment-index="2">
<img src="path/to/another/image.jpg" />
</div>
</div>
</section>
Now we add a function fadeContent() and call it every time fragmentshown or fragmenthidden is triggered.
function fadeContent($el, back=false) {
// If we go back, choose previous element, otherwise current element
$new = (back ? $el.prev() : $el);
if($new.length == 0) {
$text = $el.parent().siblings('.content-target');
$text.fadeOut(200);
} else {
$text = $new.parent().siblings('.content-target');
$text.hide(0, function() {
$text.html($new.html());
$text.fadeIn(300);
});
}
}
Reveal.addEventListener('fragmentshown', function(event) {
// Get first fragment which has "content-source" class
var $el = $($(event.fragments).filter(function() {
return $(this).hasClass('content-source')
})[0]);
// Show content
if ($el.hasClass('content-source'))
fadeContent($el);
});
Reveal.addEventListener('fragmenthidden', function(event) {
// Get first fragment which has "content-source" class
var $el = $($(event.fragments).filter(function() {
return $(this).hasClass('content-source')
})[0]);
// Hide element
if ($el.hasClass('content-source'))
fadeContent($el, back=true);
});
Since we have just extended the fragment functionality, we can still use the data-fragment-index attribute and change the order.
The solution can easily be optimized/customized.

You can specify two transitions, for entering and for leaving, thus the following works but has a glitch (image flicker when appearing) :
<section data-transition="slide none">
<img src="timeline2.a.svg">
</section>
<section data-transition="none">
<img src="timeline2.b.svg">
</section>
<section data-transition="none side">
<img src="timeline2.c.svg">
</section>
Otherwise this may also be a solution, this is a bit different from what you want but is not bad :
<section data-transition="slide fade-out">
<img src="timeline2.a.svg">
</section>
<section data-transition="fade-in fade-out">
<img src="timeline2.b.svg">
</section>
<section data-transition="fade-in side">
<img src="timeline2.c.svg">
</section>

Suppose, you are using data-background for displaying your photos, then you need to use data-background-transition="none" for the desired effect.

Related

Meteor only trigger template after selecting item from list

I have a typical scenario where there is a list view and then there's a details view. You get to the details view by selecting a list item. There's data in the record that of course will inform the layout view of the details. What I see is that the subtemplate's helper function is being called too soon (during the list view rendering) to have the data for the list details . Furthermore, it's not being called when I click an item in the list. What am I doing wrong? I'm using Meteor 0.8.2 with jQM 1.4.3.
The HTML looks as follows:
<!-- details page -->
<div data-role="page" data-theme="q" id="listdetail">
<div data-role="header" data-position="fixed">
<a data-rel="back" href="#" data-transition="slideleft" class="baack fa fa-arrow-left"></a>
<div id="detailTitle" class="headertitle"></div>
</div>
<!-- /header -->
<div data-role="content" class="ma-req-detail" id="details">
{{> qDetails}}
</div>
</div>
<!-- /details page -->
<template name="qList">
{{#each items}}
{{>qListItems}}
{{/each}}
</template>
<template name="qListItems">
<li>
<div id="msg-container-{{requestId}}" class="processing-msg">
<i class="fa fa-2x fa-times-circle"></i>
<p class="msg-text-{{requestId}}">Action pending...</p>
</div>
<a id="requestId" href="#listdetail" data-name="{{additionalProperties}}" data-transition="slide" class="{{#if isProcessing}}ui-disabled{{/if}}">
<p class="requestor">{{additionalProperties.requestedForUid}}</p>
<p class="description">week of {{additionalProperties.workWeekOf}}</p>
<p class="reqdate">total hours: </p>
</a>
</li>
</template>
<template name="qDetails" >
<div role="main" class="q-details">
<div data-role="navbar" class="week-nav">
<ul>
{{#each days}}
{{>navElements}}
{{/each}}
</ul>
</div>
</div>
<div class="ma-buttons ui-fieldcontain ui-footer-fixed">
<div data-role="controlgroup" data-type="horizontal" class="" data-position-fixed="true" data-position="bottom">
<a data-mini="true" href="#" id="approve-request"
class="ui-btn ui-btn-c ui-corner-r ui-icon-check ui-btn-icon-left ma-btn approve">Approve</a>
</div>
</div>
</template>
<template name="navElements">
<li><a id="{{day}}Nav" href="#{{day}}">{{day}}<br><span class="digital-date">{{date}}</span></a></li>
</template>
The JS bits are:
Template.qDetails.rendered = function() {
$('#details').trigger('create');
};
Template.qDetails.helpers({
days: function() {
//TODO need a way to delay this function to call when details template is shown
var dt = Session.get("record").additionalProperties.workWeekOf;
var days = [];
var weekday = new Array(7);
weekday[0] = "SAT";
weekday[1] = "SUN";
weekday[2] = "MON";
weekday[3] = "TUE";
weekday[4] = "WED";
weekday[5] = "THU";
weekday[6] = "FRI";
var dtVal = dt.getDate();
for (var i = 0; i < 7; i++) {
var dayObj = {};
dayObj.date = dtVal + i;
dayObj.day = weekday[i];
days.push(dayObj);
}
return days;
}
});
Template.qDetails.record = function () {
return Session.get("record");
};
In the code above the "days" helper function is being called when the list page is shown which results in an error because it is trying to pull the "workWeekOf" date from a record that hasn't been selected yet. How can I get to this only call once a record has been selected?
This is slightly confusing, since there's nothing in the above that shows how yourqDetails template gets rendered in the first place. However, assuming it does, you can just use:
var dt = Session.get("record") ? Session.get("record").additionalProperties.workWeekOf : [default date]
[default date] could be anything sensible (like new Date()), but you need to return something to avoid the error being thrown. This is a pretty common but very easily solved problem in Meteor - you just need a suitable default for when your Session variable or Collection isn't yet ready.

jquery mobile multiple page, tinyscrollbar missing bar

I try to implement tinyscrollbar (tinyscrollbar.js) into jquery mobile with data-role=page. On the first page it works perfectly fine. but on the second page, the bar is missing. until i clicked on inspect element. I tried tinyscrollbar_update(), update(0), slideToggle().promise()... non works. They all give me different kind of errors.
looks like this
<div class="scrollbar1">
<div class="scrollbar" style="height: 200px;">
<div class="track" style="height: 200px;">
<div class="thumb" style="top: 0px; height: 32.6530612244898px;">
<div class="end"></div>
</div>
</div>
</div>
<div class="viewport">
<div class="overview">//text</div>
</div>
<div id="footerNavigation">
Back
Next
</div>
i copied the exact same code as above to my second page code:
<div id="theNextPage" data-role="page"><!--scrollbar code--></div>
and my javascript file:
$(document).ready(function() {
$('.scrollbar1').tinyscrollbar();
});
anybody has any idea how is that so? Any way to solve it?
I have found out away!
change the class into id.
class="scrollbar1" >> id="scrollbar1"
next page:
class="scrollbar2" >> id="scrollbar2"
$(document).ready(function() {
$('#scrollbar1').tinyscrollbar();
$('#scrollbar2').tinyscrollbar();
});
$(document).on("pageshow", "#theNextPage", function () {
var $scrollbar2 = $('#scrollbar2');
$scrollbar2.tinyscrollbar();
var scrollbar1 = $scrollbar2.data("plugin_tinyscrollbar")
$scrollbar2.update(0);
});
like this would be better

How to change text color in different fragments

Say I have a simple reveal.js slide like this:
<section>
<h1>Title</h1>
<p >Text</p>
<p class="fragment">Fragment</p>
</section>
I'd like to change Text colour to red after Fragment appears on screen. How should I do it?
You could simply do
<section>
<h1>Title</h1>
<p class="fragment highlight-red" data-fragment-index="1" >Text</p>
<p class="fragment data-fragment-index="1"">Fragment</p>
</section>
That way, the red text and the "Fragment" text will always be in sync.
It's not rather simple:
<section>
<h1>Title</h1>
<p id="postfragment">Text</p>
<p class="fragment">Fragment</p>
</section>
...
<script>
Reveal.addEventListener('fragmentshown', function(event) {
document.getElementById("postfragment").style.color="red";
});
</script>
For those who want the highlight to actually appear after (rather than at the same time):
<section>
<h1>Title</h1>
<span class="fragment fade-in" data-fragment-index="1">
<p class="fragment highlight-red" data-fragment-index="3">Text</p>
</span>
<p class="fragment" data-fragment-index="2">Fragment</p>
</section>
Here is a sample of how to do it with red blue and green
<p>Highlight <span class="fragment highlight-red">red</span> <span class="fragment highlight-blue">blue</span> <span class="fragment highlight-green">green</span></p>

Jquery mobile and meteor reactive template variables not working

When I have a template variable, and I change it, the screen goes white and it goes blank.
heres a simple example:
{{#if var}}
<p align="center">reactive templating</p>
<button id="button" value="click"/>
{{else}}
<p align="center">worked!</p>
{{/if}}
Session.setDefault("var",true);
Template.content.var = function () {
return Session.get("var");
}
Template.content.events({
'click #button': function(e,t) {
Session.set("var", false);
}
});
The issue is that I have
<body>
{{>content}}
</body>
so it re-renders the whole page, including the page divs.
So do this instead...
<body>
<div data-role="page">
{{>content}}
</div>
</body>

jquerymobile - navigation in multi-page template

I have a multi-page template set up. The first page has a set of links to the inner pages - however all but one link to the same #template page which loads data dynamically. The other link is to a form which I have built within the page.
Problem I'm having is when you navigate to one of the links, continue through the pages and then use the back button to get back to the first page, if you then click the link to the form it doesn't navigate to the #page, it goes to the last page you were on in the other links.
Hope that makes sense - maybe a diagram will make it clearer
home > product list > product detail
product detail > product list > home (back button)
home > contact form
This last step shows the product detail page again, instead of the contact form. It's as if it's not clearing the page out:
My code (truncated for clarity):
<script type="text/javascript">
$(document).bind("mobileinit", function () {
$.mobile.allowCrossDomainPages = true;
$.support.cors = true;
});
$(document).delegate("#pageDetail", "pagecreate", function () {
$(this).css('background', '#ECF2FE');//`this` refers to `#pageDetail`
});
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
$.mobile.pageData = (data && data.options && data.options.pageData) ? data.options.pageData : null;
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var page;
var type;
var cat;
if ($.mobile.pageData && $.mobile.pageData.type) {
type = $.mobile.pageData.type;
}
if ($.mobile.pageData && $.mobile.pageData.cat) {
cat = $.mobile.pageData.cat;
}
if ($.mobile.pageData && $.mobile.pageData.page) {
page = $.mobile.pageData.page;
}
var url = $.url(data.toPage); // page url
var hash = url.fsegment(1).replace(/&.*/, ""); // nav hash for page holder
switch (hash) {
case "pageList":
$.ajax({
url: "http://localhost/myapp/" + type + ".aspx?type=" + type,
datatype: "html",
success: function (data) {
$('.submenu').html(data);
$('.title').html(type);
$('.submenu').trigger('create');
}
});
break;
case "pageDetail":
$('.detail').load('http://localhost/myapp/' + type + '.aspx?page=' + page + '&type=' + type + ' #contentdiv', function () {
$(this).trigger('create');
});
break;
default:
break;
}
}
});
</script>
</head>
HTML bits
<body>
<div data-role="page" id="home">
<div data-role="content">
<img src="images/Icon-Courses.png" alt="courses" /><br />
<img src="images/Icon-Contact.png" alt="contact" />
</div>
</div>
<div data-role="page" data-theme="a" id="pageList" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="pageDetail" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="contactForm" data-add-back-btn="true" class="detailpage" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Contact Us</h1>
</div>
<div data-role="content" class="detail">
<div id="contentdiv" style="margin:10px auto;width:90%;">
<label for="name">Your Name</label>
<input type="text" id="name" style="width:90%;" data-mini="true" />
<label for="email">Email Address</label>
<input type="text" id="email" style="width:90%;" data-mini="true" />
<label for="phone">Phone Number</label>
<input type="text" id="phone" style="width:90%;" data-mini="true" />
<label for="comments">Enquiry / Comments</label>
<textarea id="comments" rows="80" style="width:90%;" data-mini="true"></textarea>
<br />
<input type="submit" id="submit" value="Send Enquiry Now" />
</div>
</div>
</div>
</body>
</html>
I'm using the jqm.page.params.js for handling the querystring parameters.
Is there anything obvious there? The links to the #pageDetail page are returned within the content shown on #pageList.
Don't understand why I can't seem to clear the cache and it doesn't navigate correctly to the contact form if you've been anywhere else first. if you go straight there it works fine.
Anyone shed any light? BTW this needs to work using PhoneGap as a standalone
Thanks
Actually feel a bit dim about this, but it's because I had the same class names on multiple divs and it was using those to import data - so when it was loading data remotely it was putting it in more than one place, overwriting the form at the same time...

Resources