How to responsively position images diagonally (react) - css

I originally created a diagonal slider using the viewport width and height to determine where each box/image needed to be both on the x and y axis. However, when I started implementing animations the performance started to suffer. This is due to the window size being a dependency of a useEffect.
I am curious to know if there may be a better approach to spacing out the items diagonally that doesn't cause a re-render when the browser window resizes. Would grid or possibly flexbox be a better route or possibly something else. I've linked my sandbox below.
Sandbox: https://codesandbox.io/s/relaxed-butterfly-zot2xe?file=/components/Images.js
Animated Sandbox: https://codesandbox.io/s/mystifying-spence-m52t2p?file=/components/Images.js

This is a matter of slope. Slope is the incline of a line, which is being conducted by 2 points (a line segment, rather).
Now, the first thing that I see in your question is:
responsively
Usually when we are dealing with responsive stuff we use percentages, because they automatically adjust on resizes etc. So we know that m = rise/run, m being slope. Let's say you wanted your slope to be -2, for example.
Your coordinates would be (starting at [0, 0]):
[0, 0]
[2, 1]
[4, 2]
[6, 3]
[8, 4]
NOTE: I DID NOT USE NEGATIVES BECAUSE [0, 0] IS ON THE TOP LEFT, THERE ARE NO NEGATIVES.

You can get CSS to do much of the work for you by placing the items in a grid and rotating that diagonally.
A continuous flow of items can be obtained by having two copies and transforming the slider just 50% of its length rather than the full 100%.
A bit of JavaScript is needed initially to work out what angle the slider should be at - this depends on the height and width of the viewport.
This snippet recalculates the angle on a reload or a resize. After that JS is not involved and the system should be able to optimise the use of processor power, for example by leaving the transformation to a GPU.
function init() {
const container = document.querySelector('.container');
const items = document.querySelectorAll('.item');
const w = window.getComputedStyle(container).width.replace('px', '') / 2;
const h = window.getComputedStyle(container).height.replace('px', '');
const rad = Math.atan(h / w);
container.style.setProperty('--rad', -rad + 'rad');
container.style.setProperty('--cols', items.length);
}
window.onload = init;
window.onresize = init;
* {
margin: 0;
overflow: hidden;
}
.container {
transform-origin: center center;
transform: translateY(calc(50vh)) rotate(var(--rad));
transform-origin: 0 50%;
overflow: hidden;
width: 200vmax;
height: 100vh;
position: relative;
display: flex;
align-items: center;
}
.slider {
grid-template-columns: repeat(var(--cols), 1fr);
display: grid;
animation: move 20s linear infinite;
width: 100%;
overflow: hidden;
position: relative;
}
#keyframes move {
0% {
transform: translateX(-50%);
}
100% {
transform: translateX(0);
}
}
.item {
width: 100%;
aspect-ratio: 1 / 1;
background-size: 50% auto;
background-repeat: no-repeat;
background-position: center center;
transform: rotate(calc(-1 * var(--rad)));
}
<div class="container">
<div class="slider">
<div class="item" style="background-image: url(https://picsum.photos/id/1015/300/300);">
</div>
<div class="item" style="background-image: url(https://picsum.photos/id/1016/300/300);">
</div>
<div class="item" style="background-image: url(https://picsum.photos/id/1018/300/300);">
</div>
<!-- second copy of the items -->
<div class="item" style="background-image: url(https://picsum.photos/id/1015/300/300);">
</div>
<div class="item" style="background-image: url(https://picsum.photos/id/1016/300/300);">
</div>
<div class="item" style="background-image: url(https://picsum.photos/id/1018/300/300);">
</div>
</div>
</div>

Related

Dynamic CSS 0:N Triangles in Container

I'm working on an Electron+Vue application that draws a grid styled map. For each cell (a room) I would like to indicate walls. For NSWE I can border-left/etc. For NW/NE/SW/SE I would like to draw an angled wall or triangle. This must be done dynamically (e.g. via classes that I inject into each cell).
There seem to be a number of ways to draw triangles and that works for a single instance or multiple if I don't need it to be dynamic. How can I do this dynamically?
Here is what I'm attempting:
<!-- ... inside a component -->
<table>
<tbody>
<tr
v-for="(row, y) in grid"
:key="y"
>
<td
v-for="(room, x) in row"
:key="x"
:class="roomClass(room)"
>
<!-- I want to conditionally add a corner triangle
in one or more corners of this cell -->
</td>
</tr>
</tbody>
</table>
// Component methods
roomClass(room) {
return {
wallseast : room.walls && room.walls.includes('southeast'),
wallswest : room.walls && room.walls.includes('southwest'),
<!-- ...so on... -->
};
},
<style scoped>
td.wallseast {
/* ??? */
}
td.wallswest {
/* ??? */
}
</style>
Update: A better example of what I'm doing. Below is a image showing a map with some walls. Note the top right (or NE) corner of one of the rooms. I would like to be able to draw this on any/all corners.
I probably wouldn't use a table... but - the concept would be the same.
You could use pseudo elements to limit the markup... and make triangles, or put an element in there... and do the border-hack trick for triangles... but I'd just use an SVG/poygon to make the triangle. You'll be just dynamically generating it, so - the extra markup is fine. It is - what it is.
You should probably have a little view component. Then have all of the triangles in there, and based on hover or game-state - give them a class or whatever to let it know what triangle to show.
<room>
<svg class='arrow ne'><polygon points='100,0 100,100 0,0'></svg>
<svg class='arrow sw'><polygon points='100,100 0,100 0,0'></svg>
<arrow class='sw'>
<!-- or if you really want... you can make the shape with CSS... -->
</arrow>
</room>
https://codepen.io/sheriffderek/pen/0cdb7ccc2efd2f1f5868380d0afae436
Did you mean something like this?
//CSS
.sq-right {
width: 201px;
height: 201px;
background-color: #fff;
transform: rotate(30deg);
position: absolute;
top: -66px;
left: -136px;
text-align: center;
}
.sq-left {
width: 201px;
height: 201px;
background-color: #fff;
transform: rotate(-30deg);
position: absolute;
top: -66px;
left: 136px;
text-align: center;
}
.bg-color{
background-color: red;
height: 172px;
width: 201px;
position: absolute;
left: 142px;
top: 106px;
text-align:center;
overflow:hidden;
}
<!-- language: lang-html -->
<div id="parentDiv">
<div class="bg-color"></div>
<div class="sq-right"></div>
<div class="sq-left"></div>
</div>
<!-- end snippet -->
So in that case you have to use SVG or canvas to create multiple triangles with the help of loop.
var canvas = document.getElementById('canvas');
if (canvas.getContext)
{
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(75,75);
context.lineTo(10,75);
context.lineTo(10,25);
context.fill();
}

Scale font to size when text exceeds container?

I want font size to be 54px if the text fits inside the container, otherwise it should be 36px.
I was considering whether I can achieve this with a pure CSS solution, using the scale function to collapse to either of the two. If the container can be assumed to be full with, I guess I could use vw as a base for a calculation?
But I am very much stuck on this. Could anyone give me a hint, as to how I can achieve this or something close to it.
If you would like to put some text inside a container and have it size itself to fill that container, then CSS Tricks has an article on Fitting Text to a Container that will cover your options.
You could also use Viewport Sized Typography which take advantage of viewport units such as:
1vw = 1% of viewport width
1vh = 1% of viewport height
1vmin = 1vw or 1vh, whichever is smaller
1vmax = 1vw or 1vh, whichever is larger
One unit of any v* is 1% of the viewport axis. Where the “Viewport” == browser window size == window object. If the viewport is 50cm wide, 1vw == 0.5cm. Using viewport units alone such as font-size: 4vw; can make the text appear too big or too small when varying the window width and bring accessibility issues (as the user preferences are not taken into account).
Lastly, you could use clamp() to achieve Simplified Fluid Typography. Clamp takes three values, a min, max, and a flexible unit in the middle that it will use in case the value is between the min and max.
If you want the font size to be a minimum of 36px and maximum 54px, you could use clamp() like this and vary the "flexible unit" to your liking. Here is an example of fluid typography for an <h1> element inside a container.
body {
font-size: 1rem;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.container {
border: .2rem solid #f06;
padding: .5rem;
height: 100%;
width: auto;
}
.container h1 {
font-size: 36px; /* fallback */
font-size: clamp(36px, 10vw, 54px);
}
<body>
<div class="container">
<h1>Heading text</h1>
</div>
</body>
Browser support for clamp() is pretty good, but you’d probably want to put a font-size declaration before it to set an acceptable fallback value.
In conclusion, if you needed to set an explicit width and height for said container, you might want to use media queries along with viewport units, calc(), or clamp() depending on the size of the content box in which the text resides.
i think it's nearly impossible to calculate it without js.
below is an code example how i would do it in jquery or you could use the following jquery plugin, but i never tested this plugin before: FitText.js
for (let container of $('.text-container')){
container = $(container);
let textInner = $(container).find('.text-inner');
console.log(container.width());
console.log(textInner.width());
if (container.width()<textInner.width()){
textInner.addClass('fs-36');
textInner.removeClass('fs-54');
} else {
textInner.addClass('fs-54');
textInner.removeClass('fs-36');
}
}
.text-container,
.text-container-before{
position: relative;
width: 250px;
height: 60px;
background: #333;
color: #090;
overflow: visible;
margin: 20px 0;
}
.text-inner,
.text-before{
position: absolute;
top: 0;
left: 0;
height: 100%;
background: #0399;
word-break: keep-all;
white-space: nowrap;
}
.fs-54{
font-size: 54px;
}
.fs-36{
font-size: 36px;
}
/* just added because console window is hiding running code snipped */
.spacer{
height: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>before</h1>
<div class="text-container-before">
<div class="text-before fs-54">a bit longer text</div>
</div>
<div class="text-container-before">
<div class="text-before fs-54">shorter text</div>
</div>
<h1>after</h1>
<div class="text-container">
<div class="text-inner fs-54">a bit longer text</div>
</div>
<div class="text-container">
<div class="text-inner fs-54">shorter text</div>
</div>
<div class="spacer"></div>
This question awsers was not exactly what I was looking for, so here is a solution to scale down the text if it would exceed the size of the parent container, by making it smaller until it fits the container.
for(const element of document.getElementsByClassName("shrink"))
{
var size = parseInt(getComputedStyle(element).getPropertyValue('font-size'));
const parent_width = parseInt(getComputedStyle(element.parentElement).getPropertyValue('width'))
while(element.offsetWidth > parent_width)
{
element.style.fontSize = size + "px"
size -= 1
}
}
.container{
border:1px dashed #ccc;
display:flex;
align-items:center;
justify-content:center;
width:200px;
height:50px;
margin:10px;
}
.shrink{
white-space: nowrap;
font-size:80px;
}
<div class="container"><span class="shrink">long text to shrink</span></div>
<div class="container"><span class="shrink">small text</span></div>
try word-break: break-all;, I think this will solve your problem
Not, Sure about CSS solution. But, My JS solution can resolve your problem.
Refer below code. Class class-54 and class-36 will change based on content:
// creating node
function createNode(element) {
return document.createElement(element);
}
// creating append
function append(parent, el) {
return parent.appendChild(el);
}
var getPageTitle = document.getElementById("pageTitle"),
textLengthWidth = 0;
const getPageTitleText = getPageTitle.textContent;
getPageTitle.innerHTML = "";
for (let index = 0; index < getPageTitleText.length; index++) {
const span = createNode("span");
span.innerHTML = getPageTitleText.charAt(index);
append(getPageTitle, span);
if (index == getPageTitleText.length - 1) {
calcFontSize();
}
}
function calcFontSize() {
var listOfSpan = document.querySelectorAll("#pageTitle span");
listOfSpan.forEach(element => {
textLengthWidth += element.offsetWidth;
});
console.log("total DIV width: " + getPageTitle.offsetWidth);
console.log("total Content width: " + textLengthWidth);
getPageTitle.offsetWidth < textLengthWidth ? getPageTitle.classList.add("class-36") : getPageTitle.classList.add("class-54");
}
* {
padding: 0;
margin: 0;
font-size: 14px;
}
.title {
display: block;
width: 100vw;
}
h1, h1 span {
font-size: 54px;
}
.class-54 span {
font-size: 54px;
}
.class-36 span {
font-size: 36px;
}
<div class="title">
<h1 id="pageTitle">Hello World! Hello World!</h1>
</div>
Try this
.thingy {
font-size:54px;
overflow-wrap: break-word;
}
<h1 class="thingy">Hel000000000000000000ooo</h1>

CSS center image animation in the middle of the page

I have the following code that transitions an image from the bottom to the top, but I need this to center. On my colleagues screen it is centered, but on mine it is more at the top. Is there a way to ensure it is centered on all screens? (without breaking the rest of my CSS lol)
JS:
const showAlerts = () => {
getElements().forEach((alert) => {
focus-alert.style.display = "";
focus-alert.style.bottom = "25%"; // <--- *This is what does the transition to the 'center'*
});
};
HTML
<div class="mobile-wrapper">
<div id="mobile" class="focus-alert">
<img src="assets/images/mobile-moments-away-img.png">
<img id="mobile-close-btn" class="mobile-close-btn" src="assets/images/mobile-close-button-img.png" onclick="handleClose(this)">
<img id="mobile-continue-btn" class="mobile-continue-btn" src="assets/images/mobile-continue-now-img.png" onclick="handleGetQuotesClick(this)">
</div>
</div>
CSS:
.mobile-wrapper {
display: flex;
align-items: center;
justify-content: center;
/*height: 100vh;*/
}
#mobile {
position: fixed;
z-index: 9999;
transition: all 1s ease;
}
If I change focus-alert.style.bottom = 25% to 20% that centers on my screen but obviously wont for his. So just wondering how to make it centered in the middle?
Hello can you try margin:50% auto 50% auto; on image style, add it in your script that will help you to center image on middle of screen

Smaller horizontal scrollbar than div width? + No figcaption?

I'm trying to do image "carousel" with horizontal scroll. Pure HTML + CSS without JS.
This is my HTML:
<div class="slideshow">
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="p" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
</div>
This is my css:
.slideshow {
display: flex;
flex-wrap: nowrap;
overflow-y: hidden;
overflow-x: scroll;
//width: 80vw;
//margin: auto;
&::-webkit-scrollbar {
width: 350px;
height: 10px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 500px;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
&__fig {
flex: 0 0 auto;
height: 900px;
&-img{
height: 100%;
//width: 100%;
display: block;
}
}
}
1 - The problem is when I set the width of the .slideshow to 80vw, then naturally the scrollbar is shorter, but my images are cropped and not going full display width. When I try to adjust the width of the scrollbar with ::-webkit-scrollbar {width: 350px or 50vw or ...} exactly nothing happens.
I would like to have a scrollbar which is not full width of the div which I'm scrolling, but somehow can't figure it out.
2 - The other problem is I would like to have a figcaption at the bottom left side of the image. But somehow it doesn't show when the horizontal scroll is there. Any suggestions?
Here is the example how I would like to have it:
example image
edit: Now I finally managed to do it by adding:
&::-webkit-scrollbar-button:end:increment {
width: 50%;
display: block;
background: transparent;
}
But now the problem is that the scrollbar is not in middle, but on the left side. Margin:auto doesn't help. No idea how else to do it.
Also making img size 90% revealed the caption which is not that bad solution.
Now the only question is how to put the scroll bar in the centre.
Here is something close to the image you provided as an example. Sorry, but I really don't know how this can be achieved respecting the Pure HTML + CSS without JS criteria. I think it isn't possible at all.
So here, it uses jQuery and jQuery-ui draggable.
It uses a draggable div contained within its parent. Ondrag, it calculates the "scrolled" percentage to apply it to the scrollable width of the image slider.
For mobiles... I added the "touch punch" patch for jQuery-ui. More details about it here. I also placed the "initialisation code" in a function, so it can run on load AND on resize.
$(document).ready(function(){
function initDisplay(){
let slide_scrollWidth = $("#slide")[0].scrollWidth;
let customScrollbar_width = $("#sliderScroll_outer")[0].scrollWidth;
let percent = slide_scrollWidth/customScrollbar_width
$("#sliderScroll").css({"width":percent+"%", "left":0})
$("#slide")[0].scrollTo(0,0)
}
// On page load
initDisplay()
// Useful for mobile orientation change
window.onresize = initDisplay
$("#sliderScroll").draggable({
containment: "#sliderScroll_outer",
scroll: false,
drag: function(e){
let parentOffset = $(e.target).parent().offset().left
let offset = $(e.target).offset().left
let scrollableWidth = $(e.target).parent().width() - $(e.target).width()
let sliderPercent = (offset-parentOffset)/scrollableWidth
//console.log(sliderPercent)
let imageSliderWidth = $("#slide")[0].scrollWidth - $("#slide").width()
//console.log(imageSliderWidth)
$("#slide")[0].scrollTo(sliderPercent*imageSliderWidth,0)
}
});
});
#container{
margin: 1em;
}
#slide{
display: flex;
overflow: hidden;
}
#slide img{
margin: 0 0.5em;
}
#sliderScroll_outer{
width: 40vw;
background: lightgrey;
margin: 1em;
}
#sliderScroll{
width: 0vw;
height: 10px;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<div id="container">
<div id="slide">
<img src="https://via.placeholder.com/800x600.png">
<img src="https://via.placeholder.com/400x600.png">
<img src="https://via.placeholder.com/1000x600.png">
</div>
<div id="sliderScroll_outer">
<div id="sliderScroll"></div>
</div>
</div>
Run in full page mode or CodePen

CSS Nth Child calculate top height based on N

I am trying to use CSS to calculate the top height to add every 5+1 elements.
The following code moves a series of absolute positioned elements to their respective places.
.screen [data-app]:nth-child(5n-4) { left:0%; }
.screen [data-app]:nth-child(5n-3) { left:20%; }
.screen [data-app]:nth-child(5n-2) { left:40%; }
.screen [data-app]:nth-child(5n-1) { left:60%; }
.screen [data-app]:nth-child(5n) { left:80%; }
This creates the illusion of five columns with absolutely positioned elements. Now what I'd like to do is for the next row of five to also have top:180px added to them, and the row after that top:360px etc.. etc..
Can this be done without the need to write CSS code for the position of every single element. Some way of applying a top attribute for each group of five based on n value of the current element.
You can either use SASS or Flexbox in order to achieve the result you're looking for. In this case SASS will create a more bloated CSS-file than ideal, but will use the rules you posit, while Flexbox will be more future-proof and easily maintained.
The HTML
<div class="screen">
<div data-app>asdf1</div>
<div data-app>asdf2</div>
<div data-app>asdf3</div>
<div data-app>asdf4</div>
<div data-app>asdf5</div>
<div data-app>asdf6</div>
<div data-app>asdf7</div>
<div data-app>asdf8</div>
<div data-app>asdf9</div>
<div data-app>asdf10</div>
<div data-app>asdf11</div>
</div>
SASS
.screen {
position: relative;
}
.screen [data-app] {
$height: 180px;
$offset: 20%;
$blocks_per_row: 5;
position: absolute;
width: 20%;
#for $i from 0 through 20 {
$y: floor($i / $blocks_per_row);
$x: $i % 5;
&:nth-child(#{$y}n+#{$i}) {
left: $x * $offset;
top: $y * $height;
}
}
}
Flexbox
.screen {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-content: flex-start;
align-items: flex-start;
}
.screen [data-app] {
flex: 0 1 20%;
height: 180px;
}
As you can see, there's no upper limit in the Flexbox solution and it's very clean. I hope any of these solutions help you.

Resources