Alternating text like on Gumroad.com - javascript

I have seen this done a few times before. I can't remember the other sites, but if you know of others I'd like to see. (https://gumroad.com/)
Basically, when they do something like:
Share your 'songs' directly to your 'fans'. And the words in the quotes keep getting replaced.
I was wondering if there is a JS/Jquery library to help with that. Or otherwise I'd like some other websites that do it so I can get ideas!
Thanks.

I implemented something similar (without animations) using jQuery.
The code basically cycles data from an array every 2000ms, and adds it to a span.
Live Demo - http://jsfiddle.net/Dogbert/vF4rj/3/
JS:
var options = [
["music", "listeners"],
["software", "users"],
["films", "viewers"],
["comics", "readers"]
];
var interval = 2000;
var holder1 = $(".holder-1"), holder2 = $(".holder-2");
var currentIndex = 0;
function doIt() {
holder1.html(options[currentIndex][0]);
holder2.html(options[currentIndex][1]);
currentIndex = (currentIndex + 1) % options.length;
setTimeout(doIt, interval);
}
doIt();
HTML:
<h2>
Sell
<span class="holder-1"></span>
directly to your
<span class="holder-2"></span>
</h2>

If you are interested in their approach, it seems like this is what they are doing:
They have the header text in their code twice. One is what you see being animated. The other is hidden by setting its opacity to 0. This hidden version is what will be used to do any calculations on the displayed one.
The hidden version has its text updated by inserting new text into placeholder span tags. The displayed version has placeholders in that spot that are empty spaces, and their width is animated by using the new width from the hidden version.
The hidden version is also used to determine the positioning of the new text relative to the rest of the sentence, and based on that information, an HTML element containing that text is animated to fall in perfectly where it should.
A pretty clever approach.

What makes this work for Gumroad is the slick animation so I feel answers are incomplete without this essential element.
That being said, the d3 javascript library (d3js.org) has a very good implementation of the base functionality plus support for transitions. The key code is the following which allows you to easily specify the type of css transform you want to be applied (see the last line):
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(32," + (height / 2) + ")");
A full example can be viewed here: http://bl.ocks.org/mbostock/3808234

Related

Reliable way to determine the width of svg text styled with #font-face

I'm having an issue positioning css web-font styled, svg text elements one after another using D3. When I first position the text elements on load they'll overlap.
However, when I position the elements moments later using the same function triggered on a delay or through a button they wont overlap.
The issue appears to be with getBBox() and getBoundingClientRect() neither of which return the proper width for the element at first.
Any ideas on how to get the correct width at first?
The JS is below, an example is here:
http://bl.ocks.org/yanofsky/6618496
and the full code here:
https://gist.github.com/yanofsky/6618496
//helper function to grab transform coordinates
function transformCoordOf(elem) {
var separator = elem.attr("transform").indexOf(",") > -1 ? "," : " ";
var trans = elem.attr("transform").split(separator);
return { x: (trans[0] ? parseFloat(trans[0].split("(")[1]) : 0), y: (trans[1] ? parseFloat(trans[1].split(")")[0] ): 0) };
}
//position the elements based on the one before it
function positionElements() {
txts.filter(function(d,i){return i != 0}) //filter out the first element
.attr("transform",function(d,i){
var prev = d3.select(txts[0][i]), //use i b/c the list shifts on filter
prevWidth = parseFloat(prev.node().getBoundingClientRect().width)
prevCoords = transformCoordOf(prev);
var cur = d3.select(this),
curWidth = parseFloat(cur.node().getBoundingClientRect().width)
curCoords = transformCoordOf(cur);
var y = prevCoords.y,
x = prevCoords.x + prevWidth + 10;
return "translate("+x+","+y+")";
})
}
var names = ["apples","oranges","bananas"]
var canvas = d3.select("#content")
.append("svg")
.attr("width","600px")
.attr("height","100px");
var txts = canvas.selectAll("text").data(names)
.enter()
.append("text")
.attr("transform","translate(10,50)")
.text(function(d){return d});
positionElements()
d3.select("button").on("click",function(){positionElements()})
I haven't tried the browser load event (I also know practically nothing about web fonts), but when we ran into this issue (hacking on Chartbuilder, nonetheless), we solved it by using WebFontLoader and waiting for the active event. One wrinkle with this: the text has to appear on the page in order to ever be loaded, so you'll need a hidden span (or whatever) that uses your font.
Here's our code (we load the font from fonts.com:
WebFont.load({
monotype: {
projectId: '65980087-55e2-40ca-85ae-729fca359467',
},
active: function(name) {
$(document).ready(function() {
ChartBuilder.start();
});
}
});
Addendum: I don't think it really makes any practical difference, but we also switched to using getComputedTextLength() instead of measuring the bounding box, just because it seemed more correct. See the code here.
The font you're referencing hasn't been downloaded at the time you first render the <text> elements and measure their width. So at this point in time you're getting a smaller width that you get later on after the referenced font has been loaded.
Waiting for the browser's load event (as Robert suggests) is something worth trying, but I'm not sure that will work on all browsers. I read something earlier that implied that some browsers don't actually load remote #font-face fonts until they encounter a first usage not just the CSS declaration. But I haven't had any experience with this so I'm not positive.
Here is a related SO question that has a couple of good answers full of links about #font-face and download timing.

Vertically center a paragraph with unknown number of lines

It's not the first time I'm bumping into this problem and never have the opportunity to figure out something right.
So here's the problem:
I have 2 divs. Each of them have a title in them. Depending on the length of the title, the title may have to fit on 2 lines or not. All of this is generated from a CMS, so no way of telling if one of them will have 2 lines or one at a given time (so I can't just separate them with a class if they have 1 line or 2)
Here's the juicy part: I want the title in those two divs centered vertically. Line height won't do it because theres always the possibility of it being on 1 or 2 lines, and display:table-cell + vertical-align:middle is not compatible with IE7.
So is there a way to vertically center those titles and that will work in every browser?
Edit: Heres a photo of the situation I try to explain:
Edit #2: Here's a code sample recreating my example. We want to get them both centered. http://jsfiddle.net/m7bLj/
Edit #3:
After the answer of #mikhailvs, I came up with this code, that worked.
$(document).ready(function(){
$(".container-div").each(function(){
$(this).children('div').css('top', String($(this).height() / 2 - $(this).children('div').height() / 2) + 'px');
})
})
If you are willing to use jQuery, you can do something like this:
var div1onLoadCallback = function () {
var w = $(window);
var div1 = $('#div1');
div1.css('top', String(w.height() / 2 - div1.height() / 2) + 'px');
}
Then declare your div like <div id="div1" onload="div1onLoadCallback()"></div>
and similarly for the second div. Of coarse, you would need to modify the css position attribute for the titles for it to work (such as position: absolute).

Javascript slider problem

Hi I am currently working on a script given to me by my boss and it is not working in all browsers except IE.
Now he is using the CSS property left to animate it so he has a variable which gets the current value of left. For example lets say left is equal to -100px.
Now once it has this value it adds 10px onto the value to make it move in from the left.
Now my issue lies with parseInt() and the "px" prefix at the end of the number. it keeps returning NaN instead of the value of left.
Does anyone know how to fix this problem?
Thanks in advance
UPDATE:
Ok I have rewritten the code and it can be viewed here, by re-written I mean I have took my bosses code and changed variable names and commented it to make it easier to understand and see what it trying to be accomplished with the javascript.
Now the slider is meant to make he selected DIV slide in from the left and into place (the DIV is offet by -760px) in IE this works fine, but not in any other browser. I have however come up with a fix in a way for the other browsers. by removing the px in the stylesheet from the end of -760 it causes the DIV to appear but it does not slide in how it should.
To make it easier im going to supply the sliders html and CSS for them here to hpefully help a little.
The HTML
The CSS
try catch
try{
w=parseInt(document.getElementById("slideDiv").style.left+"make me string");
}catch(e){
w=0;
alert(e.toString());
}
You might try something like this:
// should return e.g. "10px":
var valueAsString = document.getElementById('slideDiv').style.left;
// remove "px" at the end, so w will only contain "10":
numericPartOfValue = value.substring(0, value.length - 2);
var w = parseInt(numericPartOfValue);
if(w < 0) {
w = w+10;
// Note: Updated after the comment below:
document.getElementById(slideDiv).style.left =w + "px";
}
Use parseFloat instead of parseInt

Truncating html text appropriately into a fixed size area

In a generated html page, we have a fixed size area (lets say 200 x 300) in which we need to fit in as much text as possible (like a regular paragraph of text), and if it doesn't fit, remove an appropriate number of characters and append "..." to the end.
The text is NOT in a fixed sized font, and although we are using one specific font for this text, a "generic" solution would obviously be preferred.
This looked interesting, but I'm thinking it would be very slow with this function being called for several items on a page - http://bytes.com/topic/javascript/answers/93847-neatly-truncating-text-fit-physical-dimension
The solution can use an intermix of html, css, js, and php as needed.
Suggestions on approaches are more than welcome!
I'd say that the solution you found is the best. It is, for instance, used for this jQuery plugin which autoresizes textareas as you enter text into it. I took the concept and rewrote it with jQuery for this simple test here: http://jsfiddle.net/ZDr5K/
var para = $('#para');
var height = 200;
while(para.height() >= height){
var text = para.text();
para.text(text.substring(0, text.length - 4) + '...');
}
Possible improvements would include right trimming and removing the period if the last character is a full stop. Removing word by word would also be more readable/slightly faster.
As for the function running multiple times, that would be unavoidable. The only thing you can really do with CSS here is to use :after to append the ellipses, but even that should be avoided for cross-compatibility problems.
Set the element dimensions via CSS and its overflow to "hidden".
Then, find out with this function, if the element's content is overflowing (via):
// Determines if the passed element is overflowing its bounds,
// either vertically or horizontally.
// Will temporarily modify the "overflow" style to detect this
// if necessary.
function checkOverflow(el)
{
var curOverflow = el.style.overflow;
if ( !curOverflow || curOverflow === "visible" )
el.style.overflow = "hidden";
var isOverflowing = el.clientWidth < el.scrollWidth
|| el.clientHeight < el.scrollHeight;
el.style.overflow = curOverflow;
return isOverflowing;
}
Now, in a loop remove text and check until it is not overflowing anymore. Append an ellipsis character (String.fromCharCode(8230)) to the end, but only if it was overflowing.
To avoid any flickering effects during that operation, you can try working on a hidden copy of the element, but I'm not sure if the browsers do the necessary layout calculations on an element that's not visible. (Can anyone clarify that?)

Jquery - Truncate Text by Line (not by character count)

I need a Jquery script to truncate a text paragraph by line (not by character count).
I would like to achieve an evenly truncated text-block. It should have a "more" and "less" link to expand and shorten the text paragraph. My text paragraph is wrapped in a div with a class, like this:
<div class="content">
<h2>Headline</h2>
<p>The paragraph Text here</p>
</div>
The closest solution i could find on SOF is the one below (but it`s for textarea element and does not work for me):
Limiting number of lines in textarea
Many thanks for any tips.
Ben
For a basic approach, you could take a look at the line-height CSS property and use that in your calculations. Bear in mind that this approach will not account for other inline elements that are larger than that height (e.g. images).
If you want something a bit more advanced, you can get the information about each line using getClientRects() function. This function returns a collection of TextRectangle objects with width, height and offset for each one.
See this answer here for an example (albeit an unrelated goal) of how getClientRects() works.
Update, had a bit of time to come back and update this answer with an actual example. It's basic, but you get the idea:
http://jsbin.com/ukaqu3/2
A couple of pointers:
The collection returned by getClientRects is static, it won't update automatically if the containing element's dimensions change. My example works around this by capturing the window's resize event.
For some strange standards-compliance reason that I'm not understanding, the element you call getClientRects on must be an inline element. In the example I have, I use a container div with the text in another div inside with display: inline.
I made this little jQuery code to allow me truncate text blocks by line (via CSS classes), feel free to use and comment it.
Here is the jsFiddle, which also include truncate functions by char count or word count. You can see that currently, resize the window won't refresh the block display, I'm working on it.
/*
* Truncate a text bloc after x lines
* <p class="t_truncate_l_2">Lorem ipsum magna eiusmod sit labore.</p>
*/
$("*").filter(function () {
return /t_truncate_l_/.test($(this).attr('class'));
}).each(function() {
var el = $(this);
var content = el.text();
var classList = el.attr('class').split(/\s+/);
$.each(classList, function(index, item){
if(/^t_truncate_l_/.test(item)) {
var n = item.substr(13);
var lineHeight = parseInt(el.css('line-height'));
if(lineHeight == 1 || el.css('line-height') == 'normal')
lineHeight = parseInt(el.css('font-size')) * 1.3;
var maxHeight = n * lineHeight;
var truncated = $.trim(content);
var old;
if(el.height() > maxHeight)
truncated += '...';
while(el.height() > maxHeight && old != truncated) {
old = truncated;
truncated = truncated.replace(/\s[^\s]*\.\.\.$/, '...');
el.text(truncated);
}
}
});
});
why not make the p element with overflow: hidden; give fixed line height, caluclate the height of the div so id contains exactly the number of lines you require and the only change the height of the p from javascript.
p{
overflow:hidden;
line-height:13px;
height:26px; /* show only 2 rows */
}
<script type="text/javascript">
function seeMoreRows(){
$(p).height("52px");
}
</script>
I made a small module that works with pure text content, no nested tags and no css-padding on the text-containing element is allowed (but this functionality could easily be added).
The HTML:
<p class="ellipsis" data-ellipsis-max-line-count="3">
Put some multiline text here
</p>
The Javascript/Jquery:
( function() {
$(document).ready(function(){
store_contents();
lazy_update(1000);
});
// Lazy update saves performance for other tasks...
var lazy_update = function(delay) {
window.lazy_update_timeout = setTimeout(function(){
update_ellipsis();
$(window).one('resize', function() {
lazy_update(delay);
});
}, delay);
}
var store_contents = function(){
$('.ellipsis').each(function(){
var p = $(this);
p.data('ellipsis-storage', p.html());
});
}
var update_ellipsis = function(){
$('.ellipsis').each(function(){
var p = $(this);
var max_line_count = p.data('ellipsis-max-line-count');
var line_height = p.html('&nbsp').outerHeight();
var max_height = max_line_count*line_height;
p.html(p.data('ellipsis-storage'));
var p_height = p.outerHeight();
while(p_height > max_height){
var content_arr = p.html().split(' ');
content_arr.pop();
content_arr.pop();
content_arr.push('...');
p.html(content_arr.join(' '));
p_height = p.outerHeight();
}
});
}
} )();
I hope you like it!
If you used a monospaced font, you'd have a shot at this working, as you'd have a good idea how many letters fit onto each line, for an element of a defined width. However, if a word breaks across lines, then this might get tricky..
e: found another question which is basically what you're after - they didn't really have a resolution either, but to my mind, the line-height and element height seems closest.
"How can I count text lines inside a dom element"
tl;dr - set a height on your container div and then use the jQuery dotdotdot plugin
Was about to make #Andy E's awesome example into a plugin, but then realized https://github.com/BeSite/jQuery.dotdotdot could pull this off. Our use case is we want to show one line on desktop widths and two lines on mobile/tablet.
Our CSS will just set the container div to the equivalent of one or two line-height's accordingly and then the dotdotdot plugin appears to handle the rest.

Categories

Resources