CSS SVG Wave shape - css

that's the result I'm trying to achieve
and here's what I've done: https://codepen.io/demedos/pen/gjQNOM
HTML structure:
.container
.header
.page-1
#wave
#dot
#text
There is some problem though:
Items are positioned using absolute positioning, while I want them anchored to the main wavy line
Containers are smaller than their content
I want the line to be at 50% of the screen, filling the above space with its background color

Here's a solution using a little bit of Javascript. I've simplified your example to keep things clear.
I want the line to be at 50% of the screen, filling the above space with its background color
We use a vertical flex box arrangement to fill the height of the screen.
We set the viewBox to fit the wave curve and let the browser do normal SVG centering.
We use a very tall wave path and rely on SVG overflowing to make the wave fill to the top of the cell.
We use SVGPathElement.getPointAtLength() to calculate the correct position on the path for each dot.
function positionDot(dotId, fractionAlongWave) {
// Get a reference to the "wave-curve" path.
var waveCurve = document.getElementById("wave-curve");
// Get the length of that path
var curveLength = waveCurve.getTotalLength();
// Get the position of a point that is "fractionAlongWave" along that path
var pointOnCurve = waveCurve.getPointAtLength(curveLength * fractionAlongWave);
// Update the dot position
var dot = document.getElementById(dotId);
dot.setAttribute("cx", pointOnCurve.x);
dot.setAttribute("cy", pointOnCurve.y);
}
// Position the first dot 25% the way along the curve
positionDot("dot1", 0.25);
// Position the second dot 90% of the way along the curve
positionDot("dot2", 0.90);
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
display: flex;
flex-direction: column;
width: 640px;
height: 100vh;
margin: 0 auto;
border: 1px solid blue;
}
.header {
background-color: #333835;
}
.page-1 {
flex: 1;
border: 1px solid red;
position: relative;
}
.page-1 svg {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid red;
}
#wave {
fill:#333835;
}
#dot1,
#dot2 {
fill:#e05a5a;
}
<div class='container'>
<div class='header'>
header
</div>
<div class='page-1'>
<!-- Set the viewBox to match the curve part of the wave.
Then we can rely on the browser to centre the SVG (and thus the curve) in the parent container. -->
<svg viewBox="0 342 1366 283">
<defs>
<!-- A copy of the curve part of the wave, which we will use to calculate
the correct position of the dot using getPointAtLength(). -->
<path id="wave-curve" d="M0,342c595,0,813,283,1366,283"/>
</defs>
<!-- SVGs are "overflow: visible" by default.
If we make this path extend vertically a long way, it will fill to the
top of the SVG no matter how high the page is. -->
<path id="wave" d="M0,342c595,0,813,283,1366,283 V -10000 H 0 Z"/>
<circle id="dot1" cx="0" cy="0" r="12.5"/>
<circle id="dot2" cx="0" cy="0" r="12.5"/>
</svg>
</div>
</div>

Related

How to make an element of such a complex shape? [duplicate]

So I'm working on a site and I was wondering if it's possible to, purely using HTML5, CSS3 (and JavaScript if needed), make a div with a curved bottom, so it will look practically like this:
Or can this only be done using a background image?
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<ul class="nav">
<li>Home</li>
</ul>
</div>
</div>
</body>
There are different approaches that can be adopted to create this shape. Below is a detailed description of possibilities:
SVG Based Approaches:
SVG is the recommended way to create such shapes. It offers simplicity and scale-ability. Below are a couple of possible ways:
1- Using Path Element:
We can use SVG's path element to create this shape and fill it with some solid color, gradient or a pattern.
Only one attribute d is used to define shapes in path element. This attribute itself contains a number of short commands and few parameters that are necessary for those commands to work.
Below is the necessary code to create this shape:
<path d="M 0,0
L 0,40
Q 250,80 500,40
L 500,0
Z" />
Below is a brief description of path commands used in above code:
M command is used to define the starting point. It appears at the beginning and specify the point from where drawing should start.
L command is used to draw straight lines.
Q command is used to draw curves.
Z command is used to close the current path.
Output Image:
Working Demo:
svg {
width: 100%;
}
<svg width="500" height="80" viewBox="0 0 500 80" preserveAspectRatio="none">
<path d="M0,0 L0,40 Q250,80 500,40 L500,0 Z" fill="black" />
</svg>
2- Clipping:
Clipping means removing or hiding some parts of an element.
In this approach, we define a clipping region by using SVG's clipPath element and apply this to a rectangular element. Any area that is outside the clipping region will be hidden.
Below is the necessary code:
<defs>
<clipPath id="shape">
<path d="M0,0 L0,40 Q250,80 500,40 L500,0 Z" />
</clipPath>
</defs>
<rect x="0" y="0" width="500" height="80" fill="#000" clip-path="url(#shape)" />
Below is brief description of the elements used in above code:
defs element is used to define element / objects for later use in SVG document.
clipPath element is used to define a clipping region.
rect element is used to create rectangles.
clip-path attribute is used to link the clipping path created earlier.
Working Demo:
svg {
width: 100%;
}
<svg width="500" height="80" viewBox="0 0 500 80" preserveAspectRatio="none">
<defs>
<clipPath id="shape">
<path d="M0,0 L0,40 Q250,80 500,40 L500,0 Z" />
</clipPath>
</defs>
<rect x="0" y="0" width="500" height="80" fill="#000" clip-path="url(#shape)" />
</svg>
CSS Based Approaches:
1- Using Pseudo Element:
We can use ::before or ::after pseudo element to create this shape. Steps to create this are given below:
Create a layer with ::before OR ::after pseudo element having width and height more than its parent.
Add border-radius to create the rounded shape.
Add overflow: hidden on parent to hide the unnecessary part.
Required HTML:
All we need is a single div element possibly having some class like shape:
<div class="shape"></div>
Working Demo:
.shape {
position: relative;
overflow: hidden;
height: 80px;
}
.shape::before {
border-radius: 100%;
position: absolute;
background: black;
right: -200px;
left: -200px;
top: -200px;
content: '';
bottom: 0;
}
<div class="shape"></div>
2- Radial Gradient:
In this approach we will use CSS3's radial-gradient() function to draw this shape on the element as a background. However, this approach doesn't produce very sharp image and it might have some jagged corners.
Required HTML:
Only single div element with some class will be required i.e.
<div class="shape"></div>
Necessary CSS:
.shape {
background-image: radial-gradient(120% 120px at 50% -30px, #000 75%, transparent 75%);
}
Working Demo:
.shape {
background: radial-gradient(120% 120px at 50% -30px, #000 75%, transparent 75%) no-repeat;
height: 80px;
}
<div class="shape"></div>
JavaScript Based Approaches:
Although not required in this case but for the sake of completeness, I'm adding this approach as well. This can be useful in some cases as well:
HTML5 Canvas:
We can draw this shape on HTML5 Canvas element using path functions:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, 40);
ctx.quadraticCurveTo(311, 80, 622, 40);
ctx.lineTo(622, 0);
ctx.fill();
<canvas id="canvas" width="622" height="80"></canvas>
Below is a brief description of the methods used in above code:
beginPath() is used to create a new path. Once a new path is created, future drawing commands are directed into the path.
moveTo(x, y) moves the pen to the coordinates specified by x and y.
lineTo(x, y) draws a straight line from the current pen position to the point specified by x and y.
quadraticCurveTo(cp1x, cp1y, x, y) draws a curve from current pen position to the point specified by x and y using control point specified by cp1x and cp1y.
fill() fills the current path using non-zero or even-odd winding rule.
Useful Resources:
Radial Gradient: Specs, MDN
SVG: Specs, MDN
HTML5 Canvas: Specs, MDN
CSS:
div{
background-color:black;
width:500px;
height:50px;
border-bottom-left-radius:50%;
border-bottom-right-radius:50%;
}
see is this ok for you
div {
background-color: black;
width: 500px;
height: 50px;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
}
<div>
</div>
This is what you want:
div{
background-color: black;
width: 500px;
height: 300px;
border-radius: 0 0 50% 50% / 50px;
}
Unlike the accepted answer, this works even when the height of the div is increased.
Demo: jsFiddle
Yes, you can do this in CSS - basically make your div wider than the page to fix the too-rounded edges, then left-positioned to compensate, with bottom border radius using both x & y values, and negative bottom margin to compensate for the gap:
.round-bottom {
border-bottom-left-radius: 50% 200px; // across half & up 200px at left edge
border-bottom-right-radius: 50% 200px; // across half & up 200px at right edge
width: 160%; overflow: hidden; // make larger, hide side bits
margin-bottom: -50px; // apply negative margin to compensate for bottom gap
position: relative; left:-30%; // re-position whole element so extra is on each side (you may need to add display:block;)
}
.round-bottom {
border-bottom-left-radius: 50% 150px !important;
border-bottom-right-radius: 50% 150px !important;
position: relative;
overflow: hidden;
width: 160%;
margin-bottom:-50px;
left:-30%;
background-color:#444;
background-image: url('https://upload.wikimedia.org/wikipedia/commons/a/a2/Tropical_Forest_with_Monkeys_A10893.jpg'); background-position: center center;
background-size: 42% auto;
height:150px;
}
.container { width: 100%; height: height:100px; padding-bottom:50px; overflow:hidden;}
<div class="container"><div class="round-bottom"></div></div>
Try this
.navbar{
border-radius:50% 50% 0 0;
-webkit-border-radius:50% 50% 0 0;
background:#000;
min-height:100px;
}
jsFiddle File

Why is this CSS rotation transform of a spinner bobbing up and down? [duplicate]

This question already has answers here:
Image inside div has extra space below the image
(10 answers)
Closed 1 year ago.
I have a simple Spinner icon I created in Figma. It should be aligned on the x, y center of the enclosing frame, which is 64x64. I have added a rotation to it using CSS here:
let rotation = 0;
function incrementAnimationFrame() {
rotation = (6 + rotation) % 360;
$('#rotationBlock').css('transform', `rotate(${rotation}deg)`);
requestAnimationFrame(incrementAnimationFrame);
}
requestAnimationFrame(incrementAnimationFrame);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="rotationBlock" style="display: block; width: 160px; padding: 0px; margin: 0px;">
<svg preserveAspectRatio="xMidYMid meet" fill="#FFD041" height="160px" viewBox="0 0 64 64">
<path d="M35.6561 2.22362C43.2296 3.15353 50.1642 6.9344 55.0485 12.7967C59.9328 18.659 62.3997 26.1622 61.9472 33.7792C61.4947 41.3962 58.1567 48.5545 52.6126 53.7972C47.0686 59.04 39.7351 61.9732 32.1047 61.9998C24.4744 62.0265 17.1206 59.1445 11.5401 53.9406C5.95953 48.7367 2.57166 41.6019 2.06596 33.9882C1.56025 26.3746 3.97473 18.8544 8.81798 12.9581C13.6612 7.06183 20.5692 3.23264 28.1361 2.24987L28.9859 8.79255C23.0831 9.55919 17.6943 12.5463 13.9162 17.1458C10.1381 21.7454 8.25459 27.6118 8.64908 33.551C9.04357 39.4902 11.6864 45.0559 16.0396 49.1154C20.3929 53.1749 26.1294 55.423 32.0817 55.4022C38.034 55.3814 43.7547 53.0933 48.0795 49.0036C52.4043 44.9138 55.0082 39.3298 55.3612 33.3879C55.7142 27.4461 53.7898 21.593 49.9797 17.0199C46.1695 12.4468 40.76 9.49747 34.852 8.77207L35.6561 2.22362Z"></path>
</svg>
</div>
https://codepen.io/jwir3/pen/dyvEQKa
As you can see, the rotation bobs slightly up and down, which I think has something to do with the viewBox of the SVG and the alignment of the center of the Spinner.
I'm wondering if there is something I can do to alleviate this "bobbing". Is it possible that Figma doesn't quite have the center of the vector image aligned just right?
This happens because your SVG does not fit 100% into your div. The div is a bit higher than the SVG, you can see it when you inspect the element in your browser.
As a solution I would say don't rotate the div but rather the SVG directly. If you need to rotate the div (because of multiple nested elements) make sure that the dimensions of the element are correct and not too big.
btw, setting display: block for a div is not needed since this is it's default value.
let rotation = 0;
function incrementAnimationFrame() {
rotation = (6 + rotation) % 360;
$('#rotationBlock').css('transform', `rotate(${rotation}deg)`);
requestAnimationFrame(incrementAnimationFrame);
}
requestAnimationFrame(incrementAnimationFrame);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg id="rotationBlock" preserveAspectRatio="xMidYMid meet" fill="#FFD041" height="160px" viewBox="0 0 64 64">
<path d="M35.6561 2.22362C43.2296 3.15353 50.1642 6.9344 55.0485 12.7967C59.9328 18.659 62.3997 26.1622 61.9472 33.7792C61.4947 41.3962 58.1567 48.5545 52.6126 53.7972C47.0686 59.04 39.7351 61.9732 32.1047 61.9998C24.4744 62.0265 17.1206 59.1445 11.5401 53.9406C5.95953 48.7367 2.57166 41.6019 2.06596 33.9882C1.56025 26.3746 3.97473 18.8544 8.81798 12.9581C13.6612 7.06183 20.5692 3.23264 28.1361 2.24987L28.9859 8.79255C23.0831 9.55919 17.6943 12.5463 13.9162 17.1458C10.1381 21.7454 8.25459 27.6118 8.64908 33.551C9.04357 39.4902 11.6864 45.0559 16.0396 49.1154C20.3929 53.1749 26.1294 55.423 32.0817 55.4022C38.034 55.3814 43.7547 53.0933 48.0795 49.0036C52.4043 44.9138 55.0082 39.3298 55.3612 33.3879C55.7142 27.4461 53.7898 21.593 49.9797 17.0199C46.1695 12.4468 40.76 9.49747 34.852 8.77207L35.6561 2.22362Z"></path>
</svg>
Simplify your code with few line of CSS:
.box {
width:120px;
height:120px;
border-radius:50%;
background:conic-gradient(#0000 10%,#FFD041 0); /* 10% of the border transparent */
-webkit-mask:radial-gradient(farthest-side,#0000 calc(100% - 15px),#000 0); /* 15px = border thickness */
animation:r 1s linear infinite;
}
#keyframes r {
to {transform:rotate(360deg)}
}
<div class="box"></div>

How to apply clipPath to a div with the clipPath position being relative to the div position

Not sure if I've formulated the title properly, but here goes the question.
I have an SVG path of a cloud-like shape which I would like to use in CSS with the clip-path property.
<path d="M46.9819755,61.8637972 C42.0075109,66.8848566 35.0759468,70 27.4091794,70 C12.2715076,70 0,57.8557238 0,42.875 C0,32.9452436 5.3914988,24.2616832 13.4354963,19.534921 C14.8172134,8.52285244 24.3072531,0 35.8087666,0 C43.9305035,0 51.0492374,4.2498423 55.01819,10.6250065 C58.2376107,8.87215568 61.9363599,7.875 65.8704472,7.875 C78.322403,7.875 88.4167076,17.8646465 88.4167076,30.1875 C88.4167076,32.1602271 88.1580127,34.0731592 87.6723639,35.8948845 L87.6723639,35.8948845 C93.3534903,38.685457 97.2583784,44.4851888 97.2583784,51.1875 C97.2583784,60.6108585 89.5392042,68.25 80.0171204,68.25 C75.4841931,68.25 71.3598367,66.5188366 68.2822969,63.6881381 C65.5613034,65.4654463 62.3012892,66.5 58.7971106,66.5 C54.2246352,66.5 50.0678912,64.7384974 46.9819755,61.8637972 Z" fill="lightblue" />
When I add an SVG element in HTML and define <clipPath> with that path, the browser positions the clipping path in the top-left corner. If I apply a margin to the clipped element, the mask is not linked and stays in its initial position.
Other similar threads state that the clipPathUnits="objectBoundingBox" attribute should be added to the <clipPath> object, but that doesn't seem to solve my problem. I even transformed the path from absolute to relative and tried it like that, but got the same result.
Is it possible to somehow link the clipping path with the clipped element so that when positioning is applied to the clipped element, the clipping path moves as well?
Here is the relative path, if it helps:
<path d="M46.9819755,61.8637972c-4.974,5.021,-11.906,8.136,-19.573,8.136c-15.137,0,-27.409,-12.144,-27.409,-27.125c0,-9.93,5.392,-18.613,13.436,-23.34c1.381,-11.012,10.871,-19.535,22.373,-19.535c8.122,0,15.24,4.25,19.209,10.625c3.22,-1.753,6.918,-2.75,10.852,-2.75c12.452,0,22.547,9.99,22.547,22.313c0,1.972,-0.259,3.885,-0.745,5.707l0,0c5.682,2.791,9.586,8.59,9.586,15.293c0,9.423,-7.719,17.062,-17.241,17.062c-4.533,0,-8.657,-1.731,-11.735,-4.562c-2.721,1.778,-5.981,2.812,-9.485,2.812c-4.572,0,-8.729,-1.761,-11.815,-4.636z fill="lightblue" />
As well as some test HTML/CSS. (I've set the left property to 10px so that you see the clipping issue occur)
.clippedElement {
width: 200px;
height: 200px;
position: absolute;
left: 10px;
top: 0;
background-color: lightblue;
-webkit-clip-path: url(#cloudClip);
-moz-clip-path: url(#cloudClip);
clip-path: url(#cloudClip);
}
<svg>
<defs>
<clipPath id="cloudClip">
<path d="M46.9819755,61.8637972 C42.0075109,66.8848566 35.0759468,70 27.4091794,70 C12.2715076,70 0,57.8557238 0,42.875 C0,32.9452436 5.3914988,24.2616832 13.4354963,19.534921 C14.8172134,8.52285244 24.3072531,0 35.8087666,0 C43.9305035,0 51.0492374,4.2498423 55.01819,10.6250065 C58.2376107,8.87215568 61.9363599,7.875 65.8704472,7.875 C78.322403,7.875 88.4167076,17.8646465 88.4167076,30.1875 C88.4167076,32.1602271 88.1580127,34.0731592 87.6723639,35.8948845 L87.6723639,35.8948845 C93.3534903,38.685457 97.2583784,44.4851888 97.2583784,51.1875 C97.2583784,60.6108585 89.5392042,68.25 80.0171204,68.25 C75.4841931,68.25 71.3598367,66.5188366 68.2822969,63.6881381 C65.5613034,65.4654463 62.3012892,66.5 58.7971106,66.5 C54.2246352,66.5 50.0678912,64.7384974 46.9819755,61.8637972 Z"
/>
</clipPath>
</defs>
</svg>
<div class="clippedElement"></div>
Thanks to Robert's comment, I was able to solve the issue I had.
Here is a PHP snippet I used to convert the absolute path to relative, so that the values are between 0 and 1.
$absolute_path = "M46.9819755,61.8637972 C42.0075109,66.8848566 35.0759468,70 27.4091794,70 C12.2715076,70 0,57.8557238 0,42.875 C0,32.9452436 5.3914988,24.2616832 13.4354963,19.534921 C14.8172134,8.52285244 24.3072531,0 35.8087666,0 C43.9305035,0 51.0492374,4.2498423 55.01819,10.6250065 C58.2376107,8.87215568 61.9363599,7.875 65.8704472,7.875 C78.322403,7.875 88.4167076,17.8646465 88.4167076,30.1875 C88.4167076,32.1602271 88.1580127,34.0731592 87.6723639,35.8948845 L87.6723639,35.8948845 C93.3534903,38.685457 97.2583784,44.4851888 97.2583784,51.1875 C97.2583784,60.6108585 89.5392042,68.25 80.0171204,68.25 C75.4841931,68.25 71.3598367,66.5188366 68.2822969,63.6881381 C65.5613034,65.4654463 62.3012892,66.5 58.7971106,66.5 C54.2246352,66.5 50.0678912,64.7384974 46.9819755,61.8637972 Z";
function regex_callback($matches) {
static $count = -1;
$count++;
$width = 98;
$height = 70;
if($count % 2) {
return $matches[0] / $height;
} else {
return $matches[0] / $width;
}
}
$relative_path = preg_replace_callback('(\d+(\.\d+)?)', 'regex_callback', $absolute_path);
Since the clipping path is not rectangular, I couldn't divide the values with one number, but had to use the width and the height of the clipping path itself.
.clippedElement {
width: 98px;
height: 70px;
position: absolute;
left: 10px;
top: 0;
background-color: lightblue;
-webkit-clip-path: url(#cloudClip);
-moz-clip-path: url(#cloudClip);
clip-path: url(#cloudClip);
}
<svg width="98" height="70">
<defs>
<clipPath id="cloudClip" clipPathUnits="objectBoundingBox">
<path d="M0.47940791326531,0.88376853142857 C0.42864807040816,0.95549795142857 0.3579178244898,1 0.27968550408163,1 C0.12521946530612,1 0,0.82651034 0,0.6125 C0,0.47064633714286 0.055015293877551,0.34659547428571 0.13709690102041,0.2790703 C0.15119605510204,0.12175503485714 0.24803319489796,0 0.36539557755102,0 C0.44827044387755,0 0.52091058571429,0.060712032857143 0.56141010204082,0.15178580714286 C0.59426133367347,0.12674508114286 0.63200367244898,0.1125 0.67214742040816,0.1125 C0.79920819387755,0.1125 0.90221130204082,0.25520923571429 0.90221130204082,0.43125 C0.90221130204082,0.45943181571429 0.89957155816327,0.48675941714286 0.89461595816327,0.51278406428571 L0.89461595816327,0.51278406428571 C0.95258663571429,0.55264938571429 0.99243243265306,0.63550269714286 0.99243243265306,0.73125 C0.99243243265306,0.86586940714286 0.91366534897959,0.975 0.81650122857143,0.975 C0.77024686836735,0.975 0.72816159897959,0.95026909428571 0.69675813163265,0.90983054428571 C0.66899289183673,0.93522066142857 0.63572744081633,0.95 0.59997051632653,0.95 C0.55331260408163,0.95 0.51089684897959,0.92483567714286 0.47940791326531,0.88376853142857 Z"></path>
</clipPath>
</defs>
</svg>
<div class="clippedElement"></div>

CSS blur only in one direction (motion blur)

I need to dynamically blur an image on my page, but only along one axis (Y specifically). So here are my requirements:
Has to be done "live" (I can't pre-render a blurred version of the image)
Like I said, only on the Y axis (like a motion blur, but vertical)
Needs to animate in
Should work in IE9+
My first thought was to use a simple CSS filter:
img {
filter: blur(20px);
}
I can animate that by adding a transition (transition: filter 0.2s linear), but it only creates Gaussian blurs, which isn't the effect I want. The syntax doesn't support something like filter: blur(0 10px); to restrict the blur only to one axis.
Then I read that the blur filter (amongst others) is really just a shorthand for an SVG filter, which you can write manually if you want. So I created an SVG called filter.svg that specifies a 20px blur only along the Y axis (0 20):
<?xml version="1.0" standalone="no"?>
<svg width="1" height="1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="0 20" />
</filter>
</defs>
</svg>
And applied it like this:
img {
filter: url("filter.svg#blur");
}
And that works perfectly...but only in Firefox. Safari/Chrome don't support url() as a value for filter. Plus, I can't animate it because the value is a URL rather than a number, so transition doesn't work.
On top of all that, I don't think either of these approaches work in IE9.
So: is there any way to do what I'm trying to do? I've looked into using canvas as an alternative, but can't find any examples of a blur that only goes in one direction.
If I'm understanding the question right it can be donewith JQuery.
CSS3 does have it's limits and it's very limited in interactive values.
Jquery also adds cross-platform stability.
JQuery
$(document).ready(function() {
var $img = $('.image')
$img.hide();
$img.show().animate({
opacity: 100,
paddingTop: '+=80'
}, 500)
});
Here is an example of how it could work with javacript with a little
fooling around on opacity.
function myMove() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 150) {
clearInterval(id);
} else {
pos++;
elem.style.left = pos + 'px';
}
}
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove()">Click Me</button>
</p>
<div id="container">
<div id="animate"></div>
</div>

Placeholder background/image while waiting for full image to load?

I have a few images on my page. I'm finding that the page starts to render before the images have been loading (which is good), but that the visual effect is not great. Initially the user sees this:
--------hr--------
text
Then a few milliseconds later the page jumps to show this:
--------hr--------
[ ]
[ image ]
[ ]
text
Is there a simple way that I can show a grey background image of exactly the width and height that the image will occupy, until the image itself loads?
The complicating factor is that I don't know the height and width of the images in advance: they are responsive, and just set to width: 100% of the containing div. This is the HTML/CSS:
<div class="thumbnail">
<img src="myimage.jpeg" />
<div class="caption">caption</div>
</div>
img { width: 100% }
Here's a JSFiddle to illustrate the basic problem: http://jsfiddle.net/X8rTB/3/
I've looked into things like LazyLoad, but I can't help feeling there must be a simpler, non-JS answer. Or is the fact that I don't know the height of the image in advance an insurmountable problem? I do know the aspect ratio of the images.
Instead of referencing the image directly, stick it within a DIV, like the following:
<div class="placeholder">
<div class="myimage" style="background-image: url({somedynamicimageurl})"><img /></div>
</div>
Then in your CSS:
.placeholder {
width: 300;
height: 300;
background-repeat: no-repeat;
background-size: contain;
background-image: url('my_placeholder.png');
}
Keep in mind - the previous answers that recommend using a div background approach will change the semantic of your image by turning it from an img into a div background. This will result in things like no indexing of these images by a search crawler, delay in loading of these images by the browser (unless you explicitly preload them), etc.
A solution to this issue (while not using the div background approach) is to have a wrapper div to your image and add padding-top to it based on the aspect ratio of the image that you need to know in advance. The below code will work for an image with an aspect ratio of 2:1 (height is 50% of width).
<div style="width:100%;height:0; padding-top:50%;position:relative;">
<img src="<imgUrl>" style="position:absolute; top:0; left:0; width:100%;">
</div>
Of course - the major disadvantage of this approach is that you need to know the aspect ratio of the image in advance.
There is a really simple thing to check before you start looking into lazy-loading and other JavaScript. Make sure the JPEG images you are loading are saved with the 'progressive' option enabled!
This will cause them to load the image iteratively, starting with a placeholder that is low-resolution and faster to download, rather than waiting for the highest resolution data before rendering.
It's very simple...
This scenario allows to load a profile photo that defaults to a placeholder image.
You could load multi CSS background-image into an element. When an avatar photo fails, the placeholder image appears default of div.
If you're using a div element that loads via a CSS background-image, you could use this style:
#avatarImage {
background-image: url("place-holder-image.png"), url("avatar-image.png");
}
<div id="avatarImage"></div>
Feel free to copy this:
<script>
window.addEventListener("load", function () {
document.getElementById('image').style.backgroundColor = 'transparent';
});
</script>
<body>
<image src="example.example.example" alt="example" id="image" style="background-color:blue;">
</body>
I got this from here: Preloader keeps on loading and doesnt disappear when the content is loaded.
Apart from all solutions already mentioned, the last solution would be to hide the document until everything is loaded.
window.addEventListener('load', (e) => {
document.body.classList.add('loaded');
});
body {
opacity: 0;
}
body.loaded {
opacity: 1;
}
<div id="sidebar">
<img src="http://farm9.staticflickr.com/8075/8449869813_1e62a60f01_b.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-1.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-2.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-3.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-4.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-5.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-6.jpg" />
</div>
Or show some animation while everything is loading:
window.addEventListener('load', (e) => {
document.body.classList.add('loaded');
});
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 70px;
height: 70px;
-webkit-animation: spin 2s linear infinite;
/* Safari */
animation: spin 2s linear infinite;
position: absolute;
left: calc(50% - 35px);
top: calc(50% - 35px);
}
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
body :not(.loader) {
opacity: 0;
}
body .loader {
display: block;
}
body.loaded :not(.loader) {
opacity: 1;
}
body.loaded .loader {
display: none;
}
<div class="loader"></div>
<div id="sidebar">
<img src="http://farm9.staticflickr.com/8075/8449869813_1e62a60f01_b.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-1.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-2.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-3.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-4.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-5.jpg" />
<img src="https://www.nla.gov.au/sites/default/files/pic-6.jpg" />
</div>
The only thing I can think of, to minimize the jump effect on your text, is to set min-height to where the image will appear, I would say - set it to the "shorter" image you know of. This way the jump will be less evident and you won't need to use lazyLoad or so... However it doesn't completely fix your problem.
Here's one naive way of doing it,
img {
box-shadow: 0 0 10px 0 rgba(#000, 0.1);
}
You can manipulate the values, but it creates a very light border around the image that doesn't push the contents. Images can load at whatever time they want, and you get a good user experience.
Here is what I did with Tailwind CSS, but it's just CSS:
img {
#apply bg-no-repeat bg-center;
body.locale-en & {
background-image: url("data:image/svg+xml;utf8,<svg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><text x='50%' y='50%' style='font-family: sans-serif; font-size: 12px;' dominant-baseline='middle' text-anchor='middle'>Loading…</text></svg>");
}
body.locale-fr & {
background-image: url("data:image/svg+xml;utf8,<svg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><text x='50%' y='50%' style='font-family: sans-serif; font-size: 12px;' dominant-baseline='middle' text-anchor='middle'>Chargement…</text></svg>");
}
}
You can find the width and height of the images in the developer tools console, for example in Chrome you can click the cursor icon in the developer tools console and when you hover on the page it will highlight all the properties of the elements in the page.
This will help you find the width and height of the images, because if you hover on top of your images it will give you the dimensions of the image and other more properties. You can also make an individual div for each image and make the div relative to the images width and height. You can do it like this:
The main div will contain the images and also the background-div which is below the image.
HTML:
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div class=".mainDiv">
<div class="below"></div>
<img src="https://imgix.bustle.com/uploads/image/2020/2/13/da1a1ca4-95ec-40ea-83c1-4f07fac8b9b7-eqb9xdwx0auhotc.jpg" width="500"/>
</div>
</body>
</html>
CSS:
.mainDiv {
position: relative;
}
.below {
position: absolute;
background: #96a0aa;
width: 500px;
height: 281px;
}
img {
position: absolute;
}
The result will be that .below will be below the image and so when the image has trouble loading the user will instead see the grey .below div. You cannot see the .below div because it is hidden below the image. The only time you will see this is when the loading of the image is delayed. And this will solve all your problems.
I have got a way. But you will need to use JavaScript for it.
The HTML:
img = document.getElementById("img")
text = document.getElementById("text")
document.addEventListener("DOMContentLoaded", () => {
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEWIiIhYZW6zAAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC";
text.innerHTML = "Loaded but image is not";
});
window.onload = function() {
img.src = "https://media.geeksforgeeks.org/wp-content/uploads/20190913002133/body-onload-console.png";
text.innerHTML = "Image is now loaded";
};
#img {
width: 400px;
height: 300px;
}
<hr>
<img id="img" src="https://media.geeksforgeeks.org/wp-content/uploads/20190913002133/body-onload-console.png">
<p>Here is the Image</p>
<p id="text">Not Loaded</p>

Resources