Is flexbox snapping to grid with text wrapping possible? - javascript

So I like this, where it wraps but yet the boxes all align on both sides and fill the whole space.
<head>
<style>
* {
padding: 0px;
margin: 0px;
text-transform: none;
font-style: normal;
}
p {
display: flex;
flex-wrap: wrap;
width: 200px;
}
i {
flex: 1;
padding: 5px;
margin: 2px;
background: #ddd;
text-align: center;
}
</style>
</head>
<body>
<p>
<i>foo</i><i>hello</i><i>congratulations</i><i>forward</i><i>interesting</i><i>place</i><i>walk</i><i>to</i><i>anyplace</i><i>next</i><i>sophisticationism</i>
</p>
</body>
Using a mix of small and large words, it somehow figures out how to optimally lay them out so it fills the space completely.
What I would like to do now is, instead of having each box be a dynamically width'd rectangle, I would like for the boxes to "snap to a grid" so to speak. That is, imagine there was a grid of squares that stretched across each row. Sort of like this (which I've completely hardcoded, just for the sake of demonstrating what it looks like. In reality this is what my question is about, how to make this automatic using FlexBox).
<head>
<style>
* {
padding: 0px;
margin: 0px;
text-transform: none;
font-style: normal;
}
p {
display: flex;
flex-wrap: wrap;
width: 220px;
}
i {
width: 50px;
height: 50px;
padding: 5px;
margin: 2px;
background: #ddd;
text-align: center;
}
.l {
width: 114px;
}
</style>
</head>
<body>
<p>
<i>a</i><i>a</i><i>a</i><i>a</i><i class='l'>long</i><i>a</i><i class='l'>long</i><i>a</i><i class='l'>a</i><i class='l'>long</i><i>a</i><i>a</i><i>a</i><i>a</i><i>a</i><i>a</i><i>a</i>
</p>
</body>
So to rephrase, my question is how to cause flowing text (like the words in the images above) layout so (a) it fills each row, and (b) each box is a multiple of a square. That is, it snaps to the grid of a square, weather it's 1, 2, 3+ squares, rather than being 2.5 squares or 1.2345 squares or something. It always snaps to a whole block. It does this by first looking at the longer words, and calculating how many blocks it will take up. Then it stretches any shorter blocks (like the one letter "a" in the example above) so as to fill the blank space.
Wondering how this could be done with FlexBox or otherwise with CSS.

Try (I use split 1-3 words depends on length from here )
function show(n) {
text.innerHTML = '<p>' + split(text.innerText,n).map(line=> {
if(line.length==2 && line[0].length<n && line[1].length<n) {
// case for two short words
return `<i class='long'>${line[0]}</i><i class='short'>${line[1]}</i>`
} else {
return line.map(w=>`<i class='${w.length<9 ?'short':'long'}'>${w}</i>`).join('')
}
}).join('') + '</p>';
}
function split(str,n) {
let z=0, b=[], r=[], w=str.split(' ');
let s = w.map(v => v.length < n ? 1 : 2);
s.map((v,i)=>(
z+v>=4 ? (r.push(b),z=0,b=[]) : 0,
z+=v, b.push(w[i])
))
return b.length ? r.concat([b]) : r;
}
show(9) // 9 is min number of letters for long words;
.short {
flex-basis: 64px;
flex-grow: 1
}
.long {
flex-basis: 140px;
flex-grow: 2
}
* {
padding: 0px;
margin: 0px;
text-transform: none;
font-style: normal;
}
p {
display: flex;
flex-wrap: wrap;
width: 260px;
}
i {
flex: 1;
padding: 5px;
margin: 2px;
background: #ddd;
text-align: center;
}
<div id="text">a aaa foo hello congratulations forward interesting place walk to anyplace next sophisticationism aa bb cccccccccc ddd</div>

Related

Etch-A-Sketch: Columns will resize, but rows won't. (CSS Grid)

I am creating an Etch-A-Sketch project as part of The Odin Project's foundations course. I have created a 16x16 grid, and the amount of cells in the grid will change depending on the user's input to a prompt.
My problem is that the amount of cells in the columns will change, but not the amount of cells in the rows. This results in the etch-a-sketch pad being made up of rectangles rather than evenly placed squares.
For example: If the user enters "32", there will be 32 cells in each column. But still only 16 columns. I need the grid to be 32x32 instead of 32x16.
const container = document.querySelector("#container");
const buttonSize = document.querySelector("#gridsize");
const buttonRainbow = document.querySelector("#gridsize");
let cell = document.querySelectorAll('.cell');
let cellAmount = 16;
window.onload = createGrid(cellAmount);
cell.onmouseover = mouseOver();
// creates a 16x16 grid
function createGrid(e){
for (let i = 0; i < e*e; i++){
const div = document.createElement("div");
div.classList.add("cell");
container.appendChild(div);
}
}
// changes cell colors to black on mouseover
function mouseOver(){
let cell = document.querySelectorAll('.cell');
cell.forEach((cell) => {
cell.addEventListener('mouseover', function(){
cell.style.background = "black";
})
})
}
// resizes the grid and resets sketch
buttonSize.addEventListener('click', () => {
for (i = 0; i < cell.length; i++){
cell[i].style.removeProperty("black")
}
let userAmount = prompt("Select your pixel size (Default: 16)");
if (userAmount == ""){
userAmount == 16;
}
while (parseInt(userAmount) < 4 || parseInt(userAmount) > 100){
userAmount = prompt("Sorry, please enter a number between 4 and 100.");
}
cellAmount = parseInt(userAmount);
while (container.hasChildNodes()){
container.removeChild(container.firstChild);
}
createGrid(cellAmount);
mouseOver();
});
#container {
margin-top: 25px;
display: inline-grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: auto;
border: 7.5px solid black;
border-radius: 10px;
height: 575px;
width: 575px;
box-sizing: border-box;
}
.cell {
border: 1px solid black;
box-sizing: border-box;
}
#title {
display: flex;
background-color: crimson;
justify-content: center;
width: 270px;
height: 72px;
margin-left: 815px;
margin-top: 50px;
border-style: solid aqua;
border-radius: 35px;
font-size: small;
}
#buttons {
display: flex;
justify-content: center;
gap: 75px;
margin-top: 40px;
}
#gridsize {
background: black;
color: white;
border: none;
width: 100px;
height: 30px;
font-size: large;
border-radius: 20px;
font-weight: bold;
}
#rainbow {
background: black;
color: white;
border: none;
width: 100px;
font-size: large;
font-weight: bold;
border-radius: 20px;
}
#tipmessage {
text-align: center;
margin-top: 40px;
}
html {
background-color: snow;
text-align: center;
font-family: Arial, Helvetica, sans-serif;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Etch-A-Sketch.</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="title">
<h1>Etch-A-Sketch. &#128397</h1>
</div>
<div id="buttons">
<button id="gridsize">Size</button>
<button id="rainbow">Rainbow</button>
</div>
<div id="container"></div>
<div id="tipmessage">
<h3><em>'We don't make mistakes, just happy little accidents' - Bob Ross</em></h3>
</div>
</body>
<script src="script.js" defer></script>
</html>
I have tried changing the CSS grid properties for #container however this has not solved the problem. I have inspected my JS but can't seem to figure out if the problem is coming from the JS, or the CSS.
For example, I tried changing my CSS Grid's columns and rows to "auto" but this completely ruined the grid.
The problem is coming from the CSS. This part:
#container{
...
grid-template-columns: repeat(16, 1fr);
...
}
Because the grid-template-columns amount is hard-coded into the style-sheet it will not change with the new grid size received from the user. If the amount of columns is set to, say 33 and then you edit grid-template-columns via browser tools to match 33 then the divs created will appear as squares.
As it is written there must be 16 columns in the grid and no more or less.
So when you call the function that creates the grid you have to edit the grid-template-columns attribute of #container so that the grid-template-columns of the container is updated along with the amount of cells in the grid.
function createGrid(e){
container.style.gridTemplateColumns = `repeat(${e},1fr)` // This will make the divs square
for (let i = 0; i < e*e; i++){
const div = document.createElement("div");
div.classList.add("cell");
container.appendChild(div);
}
}

Chaining button clicks with jQuery

I'm trying to build a simple website that let's you click 6 different buttons. Each click of the button is supposed to trigger the display of specific content (as an img file). Clicking each of the six buttons should lead to different content. I have managed to achieve this part via Javascript getElementById.
However, to add a bit more complexity, I want to implement sequential decision making. Meaning that clicking Button "1" and THEN clicking Button "2" (or 3-6 for that matter) should each lead to the display of other specific content. Likewise clicking Button "1", then "2" and then "1" again should also display specific content. My sequential decision making is supposed to be limited to only two buttons interacting until the end of the decision is reached. So essentially, something like 1 -> 2 -> 3 can not happen, but 3 -> 6 -> 3 can happen. I hope it's not too complicated what I'm trying to do.
Anyway, here's some code I wrote trying to achieve this, but I'm fairly sure that my toggle function is not the correct way to go about it as I'm essentially simply placing pictures above each other and there is no sequency to any of the decisions made. I think to achieve this, I would need to chain the clicks, but I'm completely lost as to how to achieve that. Any help is greatly appreciated.
a:link {
color: white;
text-decoration: none;
}
a:visited {
color: white;
text-decoration: none;
}
a:hover {
color: white;
text-decoration: none;
}
a:active {
color: white;
text-decoration: none;
}
a.pos:link {
color: black;
text-decoration: none;
}
a.pos:visited {
color: black;
text-decoration: none;
}
a.pos:hover {
color: white;
text-decoration: none;
}
a.pos:active {
color: black;
text-decoration: none;
}
a.button:link, a.button:visited {
margin: auto;
position: absolute;
top: 0px;
left: 0px;
background-color: yellowgreen;
width: 345px;
line-height: 20px;
height: 185px;
border: 2px solid;
border-color: white;
text-align: center;
border-radius: 100px;
font-family: open sans;
font-size: 9px;
color: black;
font-weight: 650;
color: white;
padding: 14px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
}
a.button:hover, a.button:active {
background-color: yellowgreen;
}
body {margin:0;}
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
position: fixed;
top: 0;
width: 100%;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 20px 30px;
font-family: open sans;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #111;
}
.active {
background-color: #4CAF50;
}
h1 {
color: whitesmoke;
font-family: open sans;
font-size: 300%;
}
.table {
margin: auto;
position: relative;
width: 450px;
top: -1350px;
border: 6px solid #333333;
border-radius: 250px;
background: #737373;
padding-top: 150px;
padding-right: 50px;
padding-left: 50px;
padding-bottom: 150px;
}
#quattro {
margin: auto;
position: absolute;
bottom: -25px;
right: 250px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#uno {
margin: auto;
position: absolute;
top: -25px;
right: 250px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#duo {
margin: auto;
position: absolute;
top: 25px;
right: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#tres {
margin: auto;
position: absolute;
bottom: 25px;
right: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#cinqo {
margin: auto;
position: absolute;
bottom: 25px;
left: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#seis {
margin: auto;
position: absolute;
top: 25px;
left: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
.imgrange1 {
text-align: center;
color: white;
position: absolute;
top: 400px;
left: -400px;
}
.imgrange2 {
text-align: center;
color: white;
position: absolute;
top: 400px;
left: 320px;
}
.centered {
font-family: open sans;
font-size: 150%;
position: absolute;
top: -3%;
left: 50%;
transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="css/style.css">
<style>
.myimgdivtoggle1 {
display: none;
}
.myimgdivtoggle2 {
display: none;
}
.myimgdivtoggle3 {
display: none;
}
.myimgdivtoggle4 {
display: none;
}
.myimgdivtoggle5 {
display: none;
}
</style>
<script>
$(document).ready(function(){
$('.togglebtn1').click(function(){
$('.myimgdivtoggle1').toggle();
});
});
$(document).ready(function(){
$('.togglebtn2').click(function(){
$('.myimgdivtoggle2').toggle();
});
});
$(document).ready(function(){
$('.togglebtn3').click(function(){
$('.myimgdivtoggle3').toggle();
});
});
$(document).ready(function(){
$('.togglebtn4').click(function(){
$('.myimgdivtoggle4').toggle();
});
});
$(document).ready(function(){
$('.togglebtn5').click(function(){
$('.myimgdivtoggle5').toggle();
});
});
$(document).ready(function(){
$('.togglebtn6').click(function(){
$('.myimgdivtoggle6').toggle();
});
});
</script>
</head>
<body>
<ul>
<li><a class="active" href="index.html">Main</a></li>
<li>News</li>
<li>Contact</li>
<li>About</li>
</ul>
<div style="padding:20px;margin-top:30px;background-color:cadetblue;height:1500px;">
<h1><center>TEST</center></h1>
</div>
<div class="table">
<button type="button" class="togglebtn1" id="uno">1</button>
<div class="myimgdivtoggle1">
<img src="1.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn2" id="duo">2</button>
<div class="myimgdivtoggle2">
<img src="2.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn3" id="tres">3</button>
<div class="myimgdivtoggle3">
<img src="3.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn4" id="quattro">4</button>
<div class="myimgdivtoggle4">
<img src="4.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn5" id="cinqo">5</button>
<div class="myimgdivtoggle5">
<img src="5.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn6" id="seis">6</button>
<div class="myimgdivtoggle6">
<img src="6.JPG" class="imgrange1"/>
</div>
</body>
</html>
Though I can not provide you with a firm solution, I can however offer a small example which illustrates how to incorporate an array which tracks the buttons that have been clicked, as well as a way to get certain content from combinations of buttons.
Run the example and try the combinations 363, 254, 521 and 165 to get some results showing up. I've tried my best to show what the produced output is.
I'd suggest that you take a look at it and ask any questions if you have them. I'll check in to see if you do.
$(document).ready(function() {
/**
* Select the buttons.
* The $display and $clickedButtons are just to output
* the values that are stored.
*/
const $buttons = $('.button');
const $display = $('#display');
const $clickedButtons = $('#clicked-buttons');
const $removeButton = $('#remove-button');
/**
* Array which tracks your clicked buttons.
* If a button is clicked, the value of that button should be added to this array.
* The combination of the values will then later represent the key.
*/
const values = [];
/**
* This is where any know combinations are stored.
* The values in the values array will later be transformed into a single string to
* see if it matches any key in the combinations object below.
* If it does, it will give you a value, otherwise undefined.
*/
const combinations = {
"363": "https://www.fillmurray.com/200/200",
"254": "https://www.fillmurray.com/220/220",
"521": "https://www.fillmurray.com/240/240",
"165": "https://www.fillmurray.com/300/300"
};
/**
* Combines the values to form a single key and check if that key matches a combination.
* If there is a match the content should be anything other than undefined.
*/
function tryCombination() {
// This will output the current values from the array.
$clickedButtons.text(values);
// Transform the array into a single string.
// This will be the key to select content.
// ["1", "2", "3"] becomes "123".
const key = values.join('');
// Check if key has a match in the combinations object.
const url = combinations[key];
if (url !== undefined) {
// It does, show the content.
$display.attr('src', url);
$display.removeClass('hidden');
} else {
// It doesn't, empty the content.
$display.removeAttr('src');
$display.addClass('hidden');
}
}
/**
* Listen for the click event on all the buttons.
* When clicked, get the value of that clicked button and add that to the values array.
* It then calls the tryCombination function to evaluate if the values in the values
* array make a valid combination.
*/
$buttons.on('click', function() {
// This is the currently clicked button.
const $button = $(this);
// Get the value of the button.
const value = $button.val();
// If there already are 3 previously clicked buttons,
// then empty the array, so we can start a new combination.
if (values.length === 3) {
values.length = 0;
}
// Now add the newly clicked value.
values.push(value);
// Render and try the combination.
tryCombination();
});
/**
* Remove the last item in the values array.
* Then retry to create a valid combination.
*/
$removeButton.on('click', function() {
// Remove the last item from the values array
values.pop();
// Render and try the new combination.
tryCombination();
})
});
.container {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: 200px 1fr;
grid-gap: 1em;
border: 1px solid #d0d0d0;
background-color: #f7f7f7;
padding: 1em;
border-radius: 5px;
}
.buttons {
grid-area: 1 / 1 / 2 / 3;
}
#display {
grid-area: 2 / 1 / 3 / 2;
width: 200px;
height: 200px;
background-color: #d0d0d0;
border-radius: 5px;
}
#clicked-buttons {
grid-area: 2 / 2 / 3 / 3;
display: block;
background-color: #d0d0d0;
border-radius: 5px;
padding: 1em;
margin: 0;
}
#remove-button {
grid-area: 1 / 2 / 2 / 3;
}
.hidden {
opacity: 0;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="buttons">
<button class="button" id="1" value="1" >1</button>
<button class="button" id="2" value="2" >2</button>
<button class="button" id="3" value="3" >3</button>
<button class="button" id="4" value="4" >4</button>
<button class="button" id="5" value="5" >5</button>
<button class="button" id="6" value="6" >6</button>
</div>
<img id="display" class="hidden">
<button id="remove-button">Remove last input</button>
<code id="clicked-buttons"></code>
</div>
Edit
In the spirit of showing is better than telling; your last comment was about having a loose combination of numbers. This adds another layer of complexity.
Objects can only have keys that are strings (or Symbols) to get and set values. But in your case you'll want an array of numbers which represent the keys in any order, so plain objects are not suitable anymore.
The solution for this is the Map object. This object can have any type of key and value. So we can make a link between a combination of numbers and the images they represent (hence the name "map").
The example below uses this method. I've written a function that checks if an array of numbers is a match with any combination in the map. And if it does it return an array of images, referring to your previous comment.
Check it out. I believe this one to be more complex, so once more feel free to ask questions.
/**
* Create a Map instance.
*/
const combinations = new Map();
/**
* Values and keys are added with the set() method.
* This could still be improved with a loop setting each
* combination / images pair.
*/
combinations.set([3, 3, 6], ['https://www.fillmurray.com/200/200', 'https://www.fillmurray.com/200/200']);
combinations.set([2, 4, 5], ['https://www.fillmurray.com/220/220', 'https://www.fillmurray.com/220/220']);
combinations.set([1, 2, 5], ['https://www.fillmurray.com/240/240']);
combinations.set([1, 5, 6], ['https://www.fillmurray.com/300/300', 'https://www.fillmurray.com/300/300', 'https://www.fillmurray.com/300/300']);
const tryCombination = (key, combinations) => {
/**
* Loop over every combination.
* [combination, images] exposes the key-value pair,
* it's just a syntax to write fewer lines
*/
for (const [combination, images] of combinations) {
/**
* Create an array for the matches. If a number of the
* combination is in the given key, then that number
* will be pushed to the matches list. In the end,
* if everything matches, we should have just as many
* matches as numbers in the combination. That way
* we know if a key is correct.
*/
const matches = [];
/**
* We'll do some manipulation on the combination array,
* so to keep it intact we make a copy and manipulate that instead.
*/
const combinationCopy = Array.from(combination);
/**
* Count backwards through the combination array.
* Backwards counting is necessary when you remove items
* from the array while looping. I'd suggest you look
* into that subject.
*/
for (let i = combinationCopy.length - 1; i >= 0; i--) {
/**
* Get the current number we're looping over.
*/
const number = combinationCopy[i];
/**
* If that number is in the key array..
*/
if (key.includes(number)) {
/**
* ..then push that number to the matches array..
*/
matches.push(number);
/**
* ..and remove it from the copied combination array.
* We do this to prevent duplicate hits for cases
* where you have multiple occurrences of the same number,
* like [3, 3, 6]. When the first 3 hits, it will be removed.
* Then we have [3, 6] and we know we only need one more
* 3 and a 6.
*/
combinationCopy.splice(i, 1);
}
}
/**
* Now if every number has been matched correctly, then
* the amount of matches should be the same as the length
* of the combination. If that is the case, return the
* images. Otherwise, do nothing.
*/
if (matches.length === combination.length) {
return images;
}
}
/**
* If there are no matches, just return false, notifying the
* user that the combination is incorrect.
*/
return false;
};
console.log(tryCombination([5, 4, 2], combinations)); // Hit!
console.log(tryCombination([5, 1, 6], combinations)); // Hit!
console.log(tryCombination([2], combinations)); // Fail!
console.log(tryCombination([5, 4, 4], combinations)); // Fail!
console.log(tryCombination([3, 6, 3], combinations)); // Hit!

Why is this template literal argument changing the css grid layout?

I created a simple horizontal tile layout using css grid areas within a larger grid. The content within the tiles are dynamically created from javascript and a json file. Not every tile contains the same amount of information.
Three possible links may appear on the bottom of the tile. I used a ternary operator to include/exclude the html needed depending on the existence of the links. If the first link in the nest object is available it also pulls double duty as the link for a clickable icon on the lefthand side. Without the link present in the json file, the image becomes static and no link appears at the bottom of the tile.
For some reason if the first link is included, the image on the left side aligns to the upper left-hand corner rather than to the center. Without the first link, the image lines up perfectly.The image shown represents the current output with the first link included in one tile and the first link is not included in another tile
I cannot figure out why this is happening with the grid areas defined. The image ought to span the entire grid-areas regardless if the Link1 is present or not.
const list1List = document.getElementById('list1List');
let trackInfo = [];
async function loadTracks() {
try {
const res = await fetch('proprietaryfile.json');
trackInfo = await res.json();
displayBrowserTracks(trackInfo);
} catch (err) {
console.error(err);
}
};
function displayList1Tracks(tracks) {
const htmlString = tracks
.map((track) => {
const app = `${track.app}`
const subheading = `${track.subheading}`
if (app == "SuperThing" && subheading == "Tracks") {
return `
<li class="track">
<h6>${track.name}</h6>
${(track.blurb ? `<p id="track-blurb">${track.blurb}</p>` : '')}
${(track.links.link1 ? `<img id="track-image" src="${track.image}"></img>`:`<img id="track-image" src="${track.image}"></img>`)}
<div class="track-links">
${(track.links.link1 ? `<a class="track-links" id="link1" href="${track.links.link1}" target="_blank">Example</a>` : '')}
${(track.links.link2 ? `<a class="track-links" id="link2" href="${track.links.link2}" target="_blank">URL</a>` : '')}
${(track.links.link3 ? `<a class="track-links" id="link3" href="${track.links.link3}" target="_blank">Docs</a>` : '')}
</div>
</li>`;
}
})
.join('');
list1List.innerHTML = htmlString;
};
.track-grid {
background-color: white;
padding: 10px 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(425px, 1fr));
grid-template-areas: 'large-col-1 large-col-2';
text-align: left;
gap: 20px;
margin: 10px;
}
.large-col-1-header,
.large-col-2-header {
color: white;
background-color: #1b2646;
padding: 5px;
}
.large-col-1-col {
grid-area: large-col-1;
background-color: #eef1f3;
border-radius: 20px;
}
.alarge-col-2-col {
grid-area: otherapps;
background-color: #e6f2ff;
border-radius: 20px;
}
#link1List,
#link2List,
#link3List {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 10px;
padding: 10px;
border-radius: 8px;
}
.track {
background-color: white;
border-radius: 8px;
padding: 10px;
display: grid;
grid-template-columns: 1fr 3fr;
grid-template-areas: 'image name' 'image blurb' 'image links';
gap: 5px;
align-items: center;
justify-items: left;
}
#track-image {
grid-area: image;
width: 100px;
height: 100px;
align-self: center;
justify-self: center;
}
.track>h6 {
grid-area: name;
text-align: left;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 14px;
font-weight: 500;
padding-left: 10px;
}
#track-blurb {
grid-area: blurb;
text-align: left;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12.5px;
font-weight: 300;
}
.track-links {
grid-area: links;
}
#link1 {
margin: 10px;
}
#link2 {
margin: 10px;
}
#link3 {
margin: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/custom.css">
<title>Track Examples</title>
</head>
<body>
<div>
<h5>Tracks</h5>
<ul class="track-list" id="list1List"> </ul>
</div>
<script src="./app.js"></script>
</body>
</html>
I've changed most of the ids, classes, and some elements for this post but hopefully this paints enough of a picture.
I've tried applying different grid row values, fixing the image location, changing the spacing between the rows and columns, changing the template literal, all to no avail. What am I doing wrong here?
I believe the issue here is the depth level of #track-image caused by the inline in the template. I'm not really good with CSS grid, but I don't think it can rip the child of an element out of flow. I think you have style the direct children for layout reflow.
Modify the template literal condition to use something like:
`
${(
track.links.link1 ?
`<a id="track-image" href="${track.links.link1}" target="_blank"><img src="${track.image}"></img></a>` :
`<span id="track-image"><img src="${track.image}"></img></span>`
)}
`
That will keep the layout class at the same depth.
Side note, watch repeating id in your rendering function. Swap to classes.

How to set font size for a paragraph whose content is being constantly replaced

I want to set a box with a paragraph in it. The text of this paragraph is supposed to be replaced after every time I click on the button. And what is important to me is that this text should stay inside the box as a one line and not break. To do that I decided to use FitText.js plugin because words have different lengths (given words are just an example). By setting it as a table and table-cell I've achieved a nice align (both vertical and horizontal). Do you have any ideas why given code doesn't work as it should?
var words = ["Emily", "William Callum Smith Jr.", "Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard", "Bradley"];
var button1 = document.getElementById("give_me_a_name_please");
button1.addEventListener("click", function1);
function getRandomItem(array){
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
function function1(){
var randomWord = getRandomItem(words);
document.getElementById('word').innerHTML = randomWord;
}
$('#give_me_a_name_please').click(function() {
function resize () { $("#word").fitText(0.6); }
});
#parent_of_word {
width: 20%;
height: 20%;
top: 10%;
left: 10%;
display: flex;
position: absolute;
text-align: center;
justify-content: center;
line-height: 100%;
border: 5px solid black;
word-break: keep-all;
white-space: nowrap;
}
#word {
color: brown;
display:flex;
align-items: center;
justify-content: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/davatron5000/FitText.js/0b4183af/jquery.fittext.js"></script>
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
<button id="give_me_a_name_please">Button</button>
Edit: updated code snippet and JSFiddle with a more optimal loop.
I've adapted your example code to be a bit leaner and easier to understand. It also uses pure jQuery instead of a mix of jQuery and pure JS and won't stop at 'undefined' after pressing the button several times, resulting in an empty array. It will instead cycle through all the words again.
There are two main solutions in effect here.
One takes inspiration from this iterative approach to JavaScript Scale Text to Fit in Fixed Div where the font size starts at a fixed value, then scales down as necessary until the word's width is less than its container's width. No need for a plugin as this is extremely trivial.
The second part implements overflow:hidden so that in the event that the text is so massive that it cannot possibly be shrunk small enough (see the lower limit of 8 in the while-loop), it will still not break out of its container.
See also the JSFiddle version.
var words = [
"Emily",
"William Callum Smith Jr.",
"Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard",
"EdwaaaaaaaaaaaaaaaaaaaAAAAAAAAAaaaaaAAaaaaAAAaaaaaaaaaaaaaaaaaaaaaaaaard",
"Bradley",
"The quick brown fox"
];
var changeWord = $("#change_word");
var parentOfWord = $("#parent_of_word");
var word = $("#word");
var idx = 0;
changeWord.click(function() {
word.text(words[idx++ % words.length]);
var size = 40;
word.css("font-size", size);
while (word.width() > parentOfWord.width() && size > 8) {
word.css("font-size", --size);
}
});
#parent_of_word {
width: 200px;
height: 50%;
top: 50px;
left: 50%;
margin-left: -100px;
display: flex;
position: absolute;
text-align: center;
justify-content: center;
line-height: 100%;
border: 5px solid black;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
}
#word {
color: brown;
display: flex;
align-items: center;
justify-content: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
<button id="change_word">Button</button>
</p>
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
To have your text in one line, without breaking:
white-space: nowrap;
To center anything vertically and horizontally:
display:flex;
align-items: center;
justify-content: center;
Applied to your example:
var words = ["Emily", "William Callum Smith Jr.", "Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard", "Resize this to see how it behaves on narrower screens. Lorem ipsum dolor sit amet bla bla bla, lorem ipsum dolor sit amet bla bla bla, lorem ipsum dolor sit amet bla bla bla.", "Bradley"];
var button1 = document.getElementById("give_me_a_name_please");
button1.addEventListener("click", function1);
function getRandomItem(array){
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
function function1(){
var randomWord = getRandomItem(words) || 'No more options.';
document.getElementById('word').innerHTML = randomWord;
}
$( '#give_me_a_name_please' ).click(function() {
function resize () { $("#word").fitText(0.6);
}
});
#parent_of_word {
display: flex;
align-items: center;
justify-content: center;
/* rest optional, just to demo
* vertical alignment
*/
height: calc(100vh - 40px);
border: 1px solid red;
padding: 1rem;
}
#word {
white-space: nowrap;
border: 5px solid;
padding: 1rem;
flex: 0 1 auto;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
* {
margin: 0;
box-sizing: border-box;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/davatron5000/FitText.js/0b4183af/jquery.fittext.js"></script>
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
<button id="give_me_a_name_please">Button</button>
It's because you've defined the width of the #parent_of_word to be 20%, which of course is related to its parent element, i.e. the body element, based on the given example.
So the width of the #parent_of_word can't be just resized dynamically based on the width of its content. It can only be "visually resized" together with its parent width, but still, it will take 20% of it. Even though the used unit are % and the element's width is dynamic, it's still fixed.
So my solution would be to omit the width property and just define the padding of your choice:
Note: I've commented out the unnecessary.
var words = ["Emily", "William Callum Smith Jr.", "Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard", "Bradley"];
var button1 = document.getElementById("give_me_a_name_please");
button1.addEventListener("click", function1);
function getRandomItem(array) {
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
function function1() {
var randomWord = getRandomItem(words);
document.getElementById('word').innerHTML = randomWord;
}
$('#give_me_a_name_please').click(function() {
function resize() {
$("#word").fitText(0.6);
}
});
#parent_of_word {
display: flex;
/*width: 20%; the culprit*/
height: 20%;
padding: 0 20px; /* can also use [%] */
position: absolute;
top: 10%;
left: 10%;
/*text-align: center;*/
justify-content: center;
align-items: center; /* added; vertical alignment */
/*line-height: 100%;*/
border: 5px solid black;
/*
word-break: keep-all;
white-space: nowrap;
*/
color: brown;
}
/*
#word {
color: brown;
display: flex;
align-items: center;
justify-content: center;
}
*/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/davatron5000/FitText.js/0b4183af/jquery.fittext.js"></script>
<button id="give_me_a_name_please">Button</button>
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
Simplest solution to your problem is to change the #parent_of_word's width value to width:auto. This'll have the width automatically adjust depending on the content (auto adjusts depending on the length of the paragraph).
Working example: https://jsfiddle.net/18faLbap/4/
I usually do it by this approach when I want to keep text in a line. especially I grids.
So no matter how long your box is, it will be in one line and when you hover on it, it will bread to lines to be readable.
var words = ["Emily", "William Callum Smith Jr.", "Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard", "Bradley"];
var button1 = document.getElementById("give_me_a_name_please");
button1.addEventListener("click", function1);
function getRandomItem(array){
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
function function1(){
var randomWord = getRandomItem(words);
document.getElementById('word').innerHTML = randomWord;
}
$('#give_me_a_name_please').click(function() {
function resize () { $("#word").fitText(0.6); }
});
#parent_of_word {
width: 20%;
top: 10%;
left: 10%;
display: flex;
position: absolute;
text-align: center;
justify-content: center;
line-height: 100%;
border: 5px solid black;
word-break: keep-all;
}
#word {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: brown;
display:flex;
align-items: center;
justify-content: center;
}
#word:hover {
white-space: normal;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/davatron5000/FitText.js/0b4183af/jquery.fittext.js"></script>
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
<button id="give_me_a_name_please">Button</button>
#parent_of_word {
min-width: 20%;
width: auto;
padding: 0px 20px;
}
All I've done here is add on width: auto and set a min-width:20%. This makes the width dynamic when the text updates inside the box. Added in a little padding to make it look nice too. Hope this helps.
Seeing as you already have jQuery on the page, consider using the jquery-textfill plugin:
http://jquery-textfill.github.io/
It will ensure your text fits to your container.
There is a CSS way to fit words in the content
I basically disable the absolute width of your #parent_of_word and change the display to inline-block, so that its width will fit its content. To make it more beautiful, I add padding property. The reduced and improved version of your code is shown below.
var words = ["Emily", "William Callum Smith Jr.", "Edwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaard", "Bradley"];
var button1 = document.getElementById("give_me_a_name_please");
button1.addEventListener("click", function1);
function getRandomItem(array){
return array.splice(Math.floor(Math.random() * array.length), 1)[0];
}
function function1(){
var randomWord = getRandomItem(words);
document.getElementById('word').innerHTML = randomWord;
}
#parent_of_word {
top: 10%;
left: 10%;
display: inline-block;
position: absolute;
text-align: center;
padding: 10px;
border: 5px solid black;
word-break: keep-all;
}
#word {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: brown;
display:flex;
align-items: center;
justify-content: center;
}
#word:hover {
white-space: normal;
}
<div id="parent_of_word">
<p id="word">----------------</p>
</div>
<button id="give_me_a_name_please">Button</button>
So you no longer need the extra JavaScript library now. More knowledge can be found here

CSS/JS: split words with horizontal line in responsive design

What would be the best way to split a word in the middle (or after a specific amount of characters or syllables) and join both "word-parts" with a line. Basically imagine a very long flexible underscore.
The goal is to have "word___part" always 100% of the parent container.
Meaning it should work fully responsive when scaling down or up the browser-window.
span:first-child {
float:left;
display:inline-block;
}
span.underscore {
}
span:last-child {
float:right;
display:inline-block;
}
<span>Auto</span><span class="underscore"></span><span>mation</span>
How would you approach that? Flexbox?
Additionally the meta-goal would even be to set the word that is split apart with a dynamic-cms. Meaning the word "Automation" comes from a backend.
You can use :after pseudo-element on first span element and set align-items: flex-end; to align line at bottom of spans.
div {
width: 70%;
display: flex;
}
span:first-child {
display: flex;
align-items: flex-end;
flex: 1;
}
span:first-child:after {
content: '';
height: 1px;
background: black;
flex: 1;
}
<div>
<span>Auto</span><span>mation</span>
</div>
<div>
<span>Lorem ipsum dolor </span><span>sit.</span>
</div>
You can also use js to split string at specific word and wrap each part in span elements.
function modify(selector, word) {
var el = document.querySelector(selector);
var text = el.textContent;
var i = text.indexOf(word)
if (i != -1) {
var arr = [text.substring(0, i), text.substring(i)]
el.innerHTML = arr.map(e => '<span>' + e + '</span>').join('');
}
}
modify('.e1', 'mation')
modify('.e2', 'sit')
div {
width: 70%;
display: flex;
}
span:first-child {
display: flex;
align-items: flex-end;
flex: 1;
}
span:first-child:after {
content: '';
height: 1px;
background: black;
flex: 1;
}
<div class="e1">Automation</div>
<div class="e2">Lorem ipsum dolor sit.</div>
Apply border-bottom to .underscore along with flex-grow: 1, then adjust height and margins to fit.
.wrapper {
display: flex;
}
span.underscore {
border-bottom: 2px solid black;
flex-grow: 1;
height: 0.5em;
margin: 0 5px;
}
<div class="wrapper">
<span>Auto</span><span class="underscore"></span><span>mation</span>
</div>
You can even use a dotted border instead of solid to simulate ellipses.
A pretty simple way:
<div>
<span class="left">Auto</span>
<span class="underscore"></span>
<span class="right">mation</span>
</div>
div {
display: flex;
}
.underscore {
width: 100%;
border-bottom: 1px solid #000;
margin: 0 5px;
}
https://codepen.io/anon/pen/XaddqO
In case if background-color behind text element is a solid color:
Create background-image with linear-gradient() on parent.
Override background-color on child elements.
Working Demo:
.text {
background: linear-gradient(to top, transparent 5px, black 5px,
black 7px, transparent 7px);
justify-content: space-between;
display: flex;
}
.text span {
background: #fff;
padding: 0 5px;
}
<div class="text">
<span>Auto</span>
<span>mation</span>
</div>
The answers are good but you said Additionally the meta-goal would even be to set the word that is split apart with a dynamic-cms. Meaning the word "Automation" comes from a backend.
So yiu can use the getword() method to get the word from backend and separate it into two using javascript
You can try to run the snippet and see the output. Then change the string returned by the getword() method and run again.
var container = document.getElementById('slit-container');
var word = getWord();
var wordPartOne = word.substring(0, 4);
var wordPartTwo = word.substring(4, word.lenght);
var data = "<span>"+wordPartOne+"</span> <span>"+wordPartTwo+"</span>";
container.innerHTML = data;
function getWord(){
//Query your backend to get the word
//for test purpose I will just return a string
return "Automation"
}
div {
width: 70%;
display: flex;
}
span:first-child {
display: flex;
align-items: flex-end;
flex: 1;
}
span:first-child:after {
content: '';
height: 1px;
background: black;
flex: 1;
}
<div id="slit-container">
</div>

Categories

Resources