├── .stylelintignore
├── src
├── scss
│ ├── placeholder-loading.scss
│ ├── _variables.scss
│ └── _layout.scss
└── index.html
├── .browserslistrc
├── .gitignore
├── docs
└── imgs
│ ├── placeholder-loading-demo-1.gif
│ ├── placeholder-loading-demo-2.gif
│ └── placeholder-loading-demo-3.gif
├── .stylelintrc.js
├── .editorconfig
├── bower.json
├── .github
└── FUNDING.yml
├── LICENSE
├── dist
├── css
│ ├── placeholder-loading.min.css
│ └── placeholder-loading.css
└── index.html
├── package.json
├── gulpfile.js
└── README.md
/.stylelintignore:
--------------------------------------------------------------------------------
1 | **/dist/
2 | *.js
3 |
--------------------------------------------------------------------------------
/src/scss/placeholder-loading.scss:
--------------------------------------------------------------------------------
1 | @use "layout";
2 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 1 version
2 | > 1%
3 | maintained node versions
4 | not dead
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # misc
5 | npm-debug.log
6 |
7 | #System Files
8 | .DS_Store
9 | Thumbs.db
--------------------------------------------------------------------------------
/docs/imgs/placeholder-loading-demo-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zalog/placeholder-loading/HEAD/docs/imgs/placeholder-loading-demo-1.gif
--------------------------------------------------------------------------------
/docs/imgs/placeholder-loading-demo-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zalog/placeholder-loading/HEAD/docs/imgs/placeholder-loading-demo-2.gif
--------------------------------------------------------------------------------
/docs/imgs/placeholder-loading-demo-3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zalog/placeholder-loading/HEAD/docs/imgs/placeholder-loading-demo-3.gif
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": [
3 | "stylelint-config-standard",
4 | "stylelint-config-recommended-scss",
5 | "stylelint-config-recess-order"
6 | ],
7 | "rules": {
8 | "indentation": 4
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{json,yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | insert_final_newline = false
17 | trim_trailing_whitespace = false
18 |
19 | [pre-commit]
20 | insert_final_newline = false
21 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "placeholder-loading",
3 | "description": "Simple and flexible, css only, content placeholder loading animation.",
4 | "main": "gulpfile.js",
5 | "authors": [
6 | "Zalog (http://www.zalog.ro/)"
7 | ],
8 | "license": "MIT",
9 | "keywords": [
10 | "placeholder",
11 | "content",
12 | "flex",
13 | "scss"
14 | ],
15 | "homepage": "https://github.com/zalog/placeholder-loading",
16 | "ignore": [
17 | "**/.*",
18 | "node_modules",
19 | "bower_components",
20 | "test",
21 | "tests"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | @use "sass:color";
2 |
3 | $ph-direction: ltr !default;
4 | $ph-bg: #fff !default;
5 | $ph-color: #ced4da !default;
6 | $ph-border: 1px solid color.scale($ph-bg, $lightness: -10%) !default;
7 | $ph-border-radius: 2px !default;
8 |
9 | $ph-cols: 12 !default;
10 | $ph-cols-remove-odd: true !default;
11 | $ph-gutter: 30px !default;
12 | $ph-spacer: 15px !default;
13 |
14 | $ph-avatar-border-radius: 50% !default;
15 |
16 | $ph-animation-duration: 0.8s !default;
17 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: zalog # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with a single custom sponsorship URL
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Catalin Zalog
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dist/css/placeholder-loading.min.css:
--------------------------------------------------------------------------------
1 | /**
2 | * placeholder-loading v0.7.0
3 | * Author: Zalog (https://www.zalog.ro/)
4 | * License: MIT
5 | **/
6 | .ph-item{background-color:#fff;border:1px solid #e6e6e6;border-radius:2px;direction:ltr;display:flex;flex-wrap:wrap;margin-bottom:30px;overflow:hidden;padding:30px 15px 15px;position:relative}.ph-item,.ph-item *,.ph-item :after,.ph-item :before{box-sizing:border-box}.ph-item:before{-webkit-animation:ph-animation .8s linear infinite;animation:ph-animation .8s linear infinite;background:linear-gradient(90deg,hsla(0,0%,100%,0) 46%,hsla(0,0%,100%,.35) 50%,hsla(0,0%,100%,0) 54%) 50% 50%;bottom:0;content:" ";left:50%;margin-left:-250%;pointer-events:none;position:absolute;right:0;top:0;width:500%;z-index:1}.ph-item>*{display:flex;flex:1 1 auto;flex-flow:column;margin-bottom:15px;padding-left:15px;padding-right:15px}.ph-row{display:flex;flex-wrap:wrap;margin-top:-7.5px}.ph-row div{background-color:#ced4da;height:10px;margin-top:7.5px}.ph-row .big,.ph-row.big div{height:20px}.ph-row .empty{background-color:hsla(0,0%,100%,0)}.ph-col-2{flex:0 0 16.6666666667%}.ph-col-4{flex:0 0 33.3333333333%}.ph-col-6{flex:0 0 50%}.ph-col-8{flex:0 0 66.6666666667%}.ph-col-10{flex:0 0 83.3333333333%}.ph-col-12{flex:0 0 100%}[class*=ph-col]{direction:ltr}[class*=ph-col]>*+.ph-row{margin-top:0}[class*=ph-col]>*+*{margin-top:7.5px}.ph-avatar{background-color:#ced4da;border-radius:50%;min-width:60px;overflow:hidden;position:relative;width:100%}.ph-avatar:before{content:" ";display:block;padding-top:100%}.ph-picture{background-color:#ced4da;height:120px;width:100%}@-webkit-keyframes ph-animation{0%{transform:translate3d(-30%,0,0)}to{transform:translate3d(30%,0,0)}}@keyframes ph-animation{0%{transform:translate3d(-30%,0,0)}to{transform:translate3d(30%,0,0)}}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "placeholder-loading",
3 | "version": "0.7.0",
4 | "description": "Simple and flexible, css only, content placeholder loading animation.",
5 | "scripts": {
6 | "start": "gulp",
7 | "build": "NODE_ENV=production gulp build",
8 | "serve": "gulp serve",
9 | "commit:version": "branch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD); branch=${branch#release/}; npm --no-git-tag-version version ${branch} && git add package.json package-lock.json && git commit -m \"build: bumps version ${branch}\"",
10 | "commit:build": "npm run build && git add dist && git commit -m \"build: runs 'npm run build'\"",
11 | "commit": "npm run commit:version && npm run commit:build",
12 | "deploy:github": "git subtree push --prefix dist origin gh-pages",
13 | "deploy:npm": "npm publish",
14 | "deploy": "npm run deploy:github && npm run deploy:npm",
15 | "lint": "npm run lint:css",
16 | "lint:css": "stylelint 'src/scss/*.scss'"
17 | },
18 | "keywords": [
19 | "placeholder",
20 | "content",
21 | "flex",
22 | "scss"
23 | ],
24 | "author": "Zalog (https://www.zalog.ro/)",
25 | "license": "MIT",
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/zalog/placeholder-loading.git"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/zalog/placeholder-loading/issues"
32 | },
33 | "homepage": "https://github.com/zalog/placeholder-loading#readme",
34 | "devDependencies": {
35 | "autoprefixer": "^10.4.7",
36 | "browser-sync": "^2.27.10",
37 | "cssnano": "^5.1.12",
38 | "del": "^6.1.1",
39 | "gulp": "^4.0.2",
40 | "gulp-header": "^2.0.9",
41 | "gulp-postcss": "^9.0.1",
42 | "gulp-rename": "^2.0.0",
43 | "gulp-sass": "^5.1.0",
44 | "postcss": "^8.4.14",
45 | "sass": "^1.82.0",
46 | "stylelint": "^14.9.1",
47 | "stylelint-config-recess-order": "^3.0.0",
48 | "stylelint-config-recommended-scss": "^6.0.0",
49 | "stylelint-config-standard": "^26.0.0",
50 | "stylelint-scss": "^4.2.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/dist/css/placeholder-loading.css:
--------------------------------------------------------------------------------
1 | /**
2 | * placeholder-loading v0.7.0
3 | * Author: Zalog (https://www.zalog.ro/)
4 | * License: MIT
5 | **/
6 | .ph-item {
7 | position: relative;
8 | display: flex;
9 | flex-wrap: wrap;
10 | padding: 30px 15px 15px 15px;
11 | margin-bottom: 30px;
12 | overflow: hidden;
13 | direction: ltr;
14 | background-color: #fff;
15 | border: 1px solid rgb(229.5, 229.5, 229.5);
16 | border-radius: 2px;
17 | }
18 | .ph-item,
19 | .ph-item *,
20 | .ph-item ::after,
21 | .ph-item ::before {
22 | box-sizing: border-box;
23 | }
24 | .ph-item::before {
25 | position: absolute;
26 | top: 0;
27 | right: 0;
28 | bottom: 0;
29 | left: 50%;
30 | z-index: 1;
31 | width: 500%;
32 | margin-left: -250%;
33 | pointer-events: none;
34 | content: " ";
35 | background: linear-gradient(to right, rgba(255, 255, 255, 0) 46%, rgba(255, 255, 255, 0.35) 50%, rgba(255, 255, 255, 0) 54%) 50% 50%;
36 | -webkit-animation: ph-animation 0.8s linear infinite;
37 | animation: ph-animation 0.8s linear infinite;
38 | }
39 | .ph-item > * {
40 | display: flex;
41 | flex: 1 1 auto;
42 | flex-flow: column;
43 | padding-right: 15px;
44 | padding-left: 15px;
45 | margin-bottom: 15px;
46 | }
47 |
48 | .ph-row {
49 | display: flex;
50 | flex-wrap: wrap;
51 | margin-top: -7.5px;
52 | }
53 | .ph-row div {
54 | height: 10px;
55 | margin-top: 7.5px;
56 | background-color: #ced4da;
57 | }
58 | .ph-row .big, .ph-row.big div {
59 | height: 20px;
60 | }
61 | .ph-row .empty {
62 | background-color: rgba(255, 255, 255, 0);
63 | }
64 |
65 | .ph-col-2 {
66 | flex: 0 0 16.6666666667%;
67 | }
68 |
69 | .ph-col-4 {
70 | flex: 0 0 33.3333333333%;
71 | }
72 |
73 | .ph-col-6 {
74 | flex: 0 0 50%;
75 | }
76 |
77 | .ph-col-8 {
78 | flex: 0 0 66.6666666667%;
79 | }
80 |
81 | .ph-col-10 {
82 | flex: 0 0 83.3333333333%;
83 | }
84 |
85 | .ph-col-12 {
86 | flex: 0 0 100%;
87 | }
88 |
89 | [class*=ph-col] {
90 | direction: ltr;
91 | }
92 | [class*=ph-col] > * + .ph-row {
93 | margin-top: 0;
94 | }
95 | [class*=ph-col] > * + * {
96 | margin-top: 7.5px;
97 | }
98 |
99 | .ph-avatar {
100 | position: relative;
101 | width: 100%;
102 | min-width: 60px;
103 | overflow: hidden;
104 | background-color: #ced4da;
105 | border-radius: 50%;
106 | }
107 | .ph-avatar::before {
108 | display: block;
109 | padding-top: 100%;
110 | content: " ";
111 | }
112 |
113 | .ph-picture {
114 | width: 100%;
115 | height: 120px;
116 | background-color: #ced4da;
117 | }
118 |
119 | @-webkit-keyframes ph-animation {
120 | 0% {
121 | transform: translate3d(-30%, 0, 0);
122 | }
123 | 100% {
124 | transform: translate3d(30%, 0, 0);
125 | }
126 | }
127 |
128 | @keyframes ph-animation {
129 | 0% {
130 | transform: translate3d(-30%, 0, 0);
131 | }
132 | 100% {
133 | transform: translate3d(30%, 0, 0);
134 | }
135 | }
--------------------------------------------------------------------------------
/src/scss/_layout.scss:
--------------------------------------------------------------------------------
1 | @use "variables" as *;
2 | @use "sass:math";
3 |
4 | .ph-item {
5 | position: relative;
6 | display: flex;
7 | flex-wrap: wrap;
8 | padding: $ph-gutter ($ph-gutter * 0.5) ($ph-gutter - $ph-spacer) ($ph-gutter * 0.5);
9 | margin-bottom: $ph-gutter;
10 | overflow: hidden;
11 | direction: $ph-direction;
12 | background-color: $ph-bg;
13 | border: $ph-border;
14 | border-radius: $ph-border-radius;
15 |
16 | &,
17 | *,
18 | ::after,
19 | ::before {
20 | box-sizing: border-box;
21 | }
22 |
23 | &::before {
24 | position: absolute;
25 | top: 0;
26 | right: 0;
27 | bottom: 0;
28 | left: 50%;
29 | z-index: 1;
30 | width: 500%;
31 | margin-left: -250%;
32 | pointer-events: none;
33 | content: " ";
34 | background: linear-gradient(to right, rgba($ph-bg, 0) 46%, rgba($ph-bg, 0.35) 50%, rgba($ph-bg, 0) 54%) 50% 50%;
35 | animation: ph-animation $ph-animation-duration linear infinite;
36 | }
37 |
38 | > * {
39 | display: flex;
40 | flex: 1 1 auto;
41 | flex-flow: column;
42 | padding-right: ($ph-gutter * 0.5);
43 | padding-left: ($ph-gutter * 0.5);
44 | margin-bottom: $ph-spacer;
45 | }
46 | }
47 |
48 | .ph-row {
49 | display: flex;
50 | flex-wrap: wrap;
51 | margin-top: -($ph-spacer * 0.5);
52 |
53 | div {
54 | height: 10px;
55 | margin-top: ($ph-spacer * 0.5);
56 | background-color: $ph-color;
57 | }
58 |
59 | .big,
60 | &.big div {
61 | height: 20px;
62 | }
63 |
64 | .empty {
65 | background-color: rgba($ph-bg, 0);
66 | }
67 | }
68 |
69 | @mixin ph-make-col($size, $columns) {
70 | .ph-col-#{$size} {
71 | flex: 0 0 math.percentage(math.div($size, $columns));
72 | }
73 | }
74 |
75 | @for $i from 1 through $ph-cols {
76 | @if $ph-cols-remove-odd and $i % 2 == 0 {
77 | @include ph-make-col($i, $ph-cols);
78 | }
79 |
80 | @else if not $ph-cols-remove-odd {
81 | @include ph-make-col($i, $ph-cols);
82 | }
83 | }
84 |
85 | // TODO make this a `.ph-wrap` class for no padding, no background, no animation,
86 | // and remove direction from `.ph-item`. This is a breaking change
87 | [class*="ph-col"] {
88 | direction: $ph-direction;
89 |
90 | > * {
91 | + .ph-row {
92 | margin-top: 0;
93 | }
94 |
95 | + * {
96 | margin-top: ($ph-spacer * 0.5);
97 | }
98 | }
99 | }
100 |
101 | .ph-avatar {
102 | position: relative;
103 | width: 100%;
104 | min-width: 60px;
105 | overflow: hidden;
106 | background-color: $ph-color;
107 | border-radius: $ph-avatar-border-radius;
108 |
109 | &::before {
110 | display: block;
111 | padding-top: 100%;
112 | content: " ";
113 | }
114 | }
115 |
116 | .ph-picture {
117 | width: 100%;
118 | height: 120px;
119 | background-color: $ph-color;
120 | }
121 |
122 | @keyframes ph-animation {
123 | 0% {
124 | transform: translate3d(-30%, 0, 0);
125 | }
126 |
127 | 100% {
128 | transform: translate3d(30%, 0, 0);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const gulp = require('gulp');
4 | const csscompile = require('gulp-sass')(require('sass'));
5 | const postcss = require('gulp-postcss');
6 | const autoprefixer = require('autoprefixer');
7 | const cssnano = require('cssnano');
8 | const header = require('gulp-header');
9 | const rename = require('gulp-rename');
10 | const del = require('del');
11 | const browsersync = require('browser-sync').create();
12 |
13 | const packageJson = require('./package.json');
14 |
15 | const src = './src';
16 | const dist = './dist';
17 |
18 | const contributors = (() => {
19 | if ( typeof packageJson.contributors == "undefined" ) return false;
20 | let output = '';
21 | for (let i = 0; i < packageJson.contributors.length; i++) {
22 | if ( i > 0 ) output += ", ";
23 | output += packageJson.contributors[i];
24 | }
25 | return " * Contributors: " + output;
26 | })();
27 | let banner = [
28 | "/**",
29 | " * " + packageJson.name + " v" + packageJson.version,
30 | " * Author: " + packageJson.author,
31 | " * License: " + packageJson.license
32 | ];
33 | if ( contributors ) banner.push(contributors);
34 | banner.push(" **/", "");
35 | banner = banner.join("\n");
36 |
37 | const copyFile = (file) => {
38 | if (!file) return;
39 |
40 | return gulp.src(file, {base: src})
41 | .pipe(gulp.dest(dist))
42 | .on('end', () => console.log(`[${new Date().toTimeString().split(' ')[0]}] Finished 'copyFile' ${file}`) );
43 | };
44 |
45 | const isProduction = (process.env.NODE_ENV === 'production') ? true : false;
46 |
47 | const clean = () => del(
48 | [
49 | dist + '/**/*',
50 | '!' + dist + '/.git/'
51 | ],
52 | {force: true}
53 | );
54 |
55 | const html = () => gulp.src(src + '/*.html')
56 | .pipe(gulp.dest(dist));
57 |
58 | function css() {
59 | const stream = gulp.src(src + '/scss/placeholder-loading.scss')
60 | .pipe(csscompile())
61 |
62 | .pipe(postcss([
63 | autoprefixer()
64 | ]))
65 | .pipe(header(banner))
66 | .pipe(gulp.dest(dist + '/css/'));
67 |
68 | if (isProduction) {
69 | stream
70 | .pipe(postcss([
71 | autoprefixer(),
72 | cssnano()
73 | ]))
74 | .pipe(header(banner))
75 | .pipe(rename({suffix: '.min'}))
76 | .pipe(gulp.dest(dist + '/css/'));
77 | }
78 |
79 | stream.pipe(browsersync.stream());
80 |
81 | return stream;
82 | }
83 |
84 | const serve = () => browsersync.init({
85 | server: dist,
86 | notify: false,
87 | reloadDelay: 500,
88 | ghostMode: false,
89 | open: false
90 | });
91 |
92 | const watch = () => {
93 | gulp.watch(src + '/*.html')
94 | .on('change', copyFile);
95 | gulp.watch(src + '/scss/**/*')
96 | .on('change', gulp.series(css));
97 |
98 | gulp.watch(dist + "/*.html")
99 | .on('change', browsersync.reload);
100 | }
101 |
102 |
103 |
104 | // grouped tasks by use case
105 | const build = gulp.series(clean, html, css);
106 |
107 |
108 |
109 | exports.build = build;
110 | exports.serve = serve;
111 | exports.default = gulp.parallel(
112 | gulp.series(build, serve),
113 | watch
114 | );
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Placeholder loading
2 |
3 | Simple and flexible, css only, content placeholder loading animation.
4 |
5 | ## Demo
6 |
7 |
14 |
15 |
18 |
19 |
22 |
23 |