How can I smoothly transition CSS background images?

为君一笑 提交于 2021-02-07 02:41:15

问题


The main solution out there is:

"Just throw a loading screen up until the page is loaded".

But my goal is to build pages that present the basics very quickly, without a loading screen, and then transition in the images and fancy features when they're ready. So I'll wait till it's loaded, and then fade it in. Or I'll load in a very low res version and then fade in the high res when it's ready.

The one aspect of this practice that I have not yet figured out is how to do it with background images.

How can I achieve the affect of a smooth fade in with background images?

I'm willing to use:

  • Javascript
  • JQuery
  • Any modern JQuery library
  • CSS tricks / "hacks"

But I want to avoid:

  • Using an overlay element and fading that in on top.

回答1:


As noted in the comments by @dandavis, there's actually a CSS transition property : background-image.

The solution, utilizing the CSS background transition property:

  1. create two background images of the same size: one transparent & one that's intended. (If you don't make them the same size, you'll get this effect!);

  2. use transition: background-image 1s to cause the transition effect

  3. use Javascript to preload the image and reset the background image when it's ready. CSS will take care of the rest.

Notable Limitations

  • This does not allow for background-size manipulation ( this results in a very weird effect).

  • The images, as mentioned, must be the same size.


Working Example

var image = new Image();
image.onload = function () {
        $(".element").css("background-image", "url('" + image.src + "')");
}

image.src = "https://c1.staticflickr.com/3/2439/3728897793_ff1c78c5d9.jpg"; //image to be transitioned to
html{
    width:100%;
    height:100%;
}
body{
    width:100%;
    height:100%;
    padding:0px;
    margin:0px;
}
.element{
    width:100%;
    height:100%;
    background-image:url('http://i.imgur.com/HRV3DsM.jpg');
    -webkit-transition: background-image 5s;
}
<div class="element">
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>



回答2:


I think I have a solution!  (sorry, just very exited I got this working:)

//library (minified)
this.BgImgFader=function(){var styleRules;function getArray(str){if(str.indexOf(',')==0){str.substring(1);}if(str.lastIndexOf(',')==str.length-1){str.substring(0,str.length-1);}if(str.indexOf(',')==-1){var selectors=[str];}else{var selectors=str.split(',');}for(var i=0;i<selectors.length;i++){selectors[i]=selectors[i].trim();}return selectors;}function getStyleSheet(style){if(typeof style==='number'){return document.styleSheets[style];}else{for(var i=0;i<document.styleSheets.length;i++){var file=document.styleSheets[i].href;file=file.substring(file.lastIndexOf('/')+1);if(file.toLowerCase()==style.toLowerCase()){return document.styleSheets[i];}}}}function addStyleRule(sheet,selector,declarations){if(sheet.addRule){sheet.addRule(selector,declarations);}else{sheet.insertRule(selector+'{'+declarations+'}');}}this.prepare=function(style,selectors){var selectors=getArray(selectors);var styleSheet=getStyleSheet(style);for(var i=0;i<selectors.length;i++){addStyleRule(styleSheet,selectors[i],'position:relative;');addStyleRule(styleSheet,selectors[i]+'::after','position:absolute;top:0;right:0;bottom:0;left:0;opacity:0.0;content:"";');}};this.fade=function(style,selectors,global,opacity,endOpacity,delta){var selectors=getArray(selectors);var styleSheet=getStyleSheet(style);styleRules=styleSheet.rules?styleSheet.rules:styleSheet.cssRules;fadeOpacity(selectors,global,opacity,endOpacity,delta,[]);};function fadeOpacity(selectors,global,opacity,endOpacity,delta,rules){opacity+=delta;if(rules.length==0){for(var i=0;i<selectors.length;i++){for(var j=0;j<styleRules.length;j++){if(global&&styleRules[j].selectorText.toLowerCase().indexOf(selectors[i].toLowerCase()+'::after')!=-1){rules.push(styleRules[j]);}else if(styleRules[j].selectorText.toLowerCase()==selectors[i].toLowerCase()+'::after'){rules.push(styleRules[j]);break;}}}}for(var i=0;i<rules.length;i++){rules[i].style.opacity=opacity;}if(opacity<endOpacity){setTimeout(function(){fadeOpacity(selectors,global,opacity,endOpacity,delta,rules);},0);}else{for(var i=0;i<rules.length;i++){rules[i].style.opacity=endOpacity;}rules.length=0;}}};

//instantiate BgImgFader in global domain
var BgImgFader = new BgImgFader();

window.onload = function(){
  //prepare specified elements
  BgImgFader.prepare(0, '.exampleClass'); //style, selectors

  //fade specified elements
  BgImgFader.fade(0, '.exampleClass', true, 0, 0.5, 0.002); //style, selectors, global, startOpacity, endOpacity, delta
};
#exampleId {
  width: 300px;
  height: 200px;
  margin: 10px 0px 0px 10px;
  background-color: #AAAAAA;
}
#exampleId .exampleClass {
  width: 200px;
  height: 130px;
  padding: 5px;
}
#exampleId .exampleClass::after {background:url(https://placeimg.com/640/480/any) center/cover no-repeat;}
<div id="exampleId">
  Some other text to illustrate how this can be implemented.
  <div class="exampleClass">
    I assume you want transparent background images because you have text in the element that you do want to show from the start?
  </div>
</div>
codepen: https://codepen.io/anon/pen/QMLPbr

I basically ended up building a tiny library. Pure JavaScript, no jQuery required.


All you have to do is add these three BgImgFader lines in JS:

//instantiate BgImgFader in global domain
var BgImgFader = new BgImgFader();

window.onload = function(){
  //prepare specified elements
  BgImgFader.prepare(0, '.exampleClass'); //stylesheet, selectors

  //fade specified elements
  BgImgFader.fade(0, '.exampleClass', true, 0, 0.5, 0.002); //stylesheet, selectors, global, startOpacity, endOpacity, delta
};

And add the following to all your background-image-elements in CSS:

#example {...}
#example::after {background:url(path/to/image.png) center/cover no-repeat;}

So for every element with a background-image, you have to add the #example::after { rule and place your background-image in there. (You do NOT have to do this in HTML, only in CSS.)


I've added the library's source code as a code snippet below. Comments are in the code.
You can either paste the code at the top of your own script, or put the code in a file and in your HTML, link to that file like you would to any other library (before your own code):

<script type="text/javascript" src="path/to/bgimgfader.js"></script>

/**
 * BgImgFader - Library:
 * This library makes it possible to fade the background-image of an element.
 * The image can be faded in or out.
 * 
 * Compatibility:
 * - IE9 and higher should be fine, lower could give trouble.
 * - Older versions of FF/Chrome will probably give some problems too, but I think we can safely assume 
 *   that those who chose either one of these browsers, did so because they choose NOT to live in the past..
 * - Opera and others... I have absolutely no idea.
 * 
 * #################################################################################
 * INSTRUCTIONS---------------------------------------------------------------------
 * 1. In CSS:
 *    a. For every element with a background-image, create an '::after' rule, and put the image in there:
 *       (You don't have to create these '::after'-elements in the HTML)
 * 
 *          #element {...}
 *          #element::after {background:url(path/to/image.png) center/cover no-repeat;}
 * 
 *       The following declarations will be added by the BgImgFader, keep that in mind:
 *          #element {position:relative;}
 *          #element::after {position:absolute; top:0;right:0;bottom:0;left:0; opacity:0.0; content:"";}
 * 
 *          (The important one is 'position:relative;', the ones on the '::after'-element have no consequences) 
 * 
 * ---------------------------------------------------------------------------------
 * 2. In JavaScript:
 *    a. Instantiate the BgImgFader in the global domain:     var BgImgFader = new BgImgFader();
 * 
 * 
 *    b. Prepare the elements with a background-image:     BgImgFader.prepare(0, 'elements'); //style, selectors
 * 
 *       - style: Reference to the style sheet with the rules for the specified elements.
 *                This can be either an INTEGER for internal style sheets (0), 
 *                or a STRING of a filename for external style sheets ('style.css').
 *       - selectors: STRING reference to the selectors in the style rules.
 *                    This works the same as in the CSS, below a few examples.
 *                         Individual tags:     ('div')	        ('#id')          ('.class')
 *                           Multiple tags:     ('div.class')   ('#id .class')   ('.class.subclass')
 *                      Multiple selectors:     ('div, #id, div.class, #id .class, .class.subclass')
 * 
 * 
 *    c. Initiate the fade:     BgImgFader.fade('style.css', 'elements', true, 0, 0.5, 0.005); //style, selectors, global, startOpacity, endOpacity, delta
 * 
 *       - style: See 2b for the details.
 *       - selectors: See 2b for the details.
 *       - global: BOOLEAN that deternimes whether only complete matches for the selectors are allowed, 
 *                 or partial matches as well, increasing the range of the BgImgFader.
 *                 TRUE allowes partial matches: feed the BgImgFader '.class' and it will also try to fade 'div .class'.
 *                 FALSE allowes only complete matches.
 *       - startOpacity: FLOAT that indicates the start opacity (0.0  -  1.0).
 *       - endOpacity: FLOAT that indicates the end opacity (0.0  -  1.0).
 *       - delta: FLOAT that indicates the delta of every fade-iteration (1.0  -  0.00000000...1).
 *                The effective range is approximately (0.1  -  0.0001).
 *                A smaller delta means a slower fade.
 *                A positive delta in combination with start<end fades the image in.
 *                A negative delta in combination with start>end fades the image out.
 * 
 * #################################################################################
 */

this.BgImgFader = function() {
  var styleRules;
  
//GET/SET-FUNCTIONS=================================================================
//GET SELECTORS---------------------------------------------------------------------
  function getArray(str) {
    /* This function is invoked by this.prepare() and this.fade().
     * This function converts the specified string of selectors to an array, and returns that.
     */
    //strip trailing comma's
    if (str.indexOf(',')==0) {str.substring(1);} //strip first comma
    if (str.lastIndexOf(',')==str.length-1) {str.substring(0,str.length-1);} //strip last comma
    //store selectors in array
    if (str.indexOf(',')==-1) {var selectors = [str];}
    else {var selectors = str.split(',');}
    //trim trailing spaces
    for (var i=0; i<selectors.length; i++) {
      selectors[i] = selectors[i].trim();
    }
    return selectors;
  }
//GET STYLE SHEET-------------------------------------------------------------------
  function getStyleSheet(style) {
    /* This function is invoked by this.prepare() and this.fade().
     * This function returns a reference to the specified style sheet, 
     * based on either a number or a filename of the sheet.
     * A number is for internal sheets, where the number stands 
     * for its location in the HTML (e.g. the first '<style></style>').
     * A filename is for external sheets (e.g. 'style.css').
     * See the instructions in the header of this file for details.
     */
    if (typeof style === 'number') {
      return document.styleSheets[style];
    } else {
      //find style sheet
      for (var i=0; i<document.styleSheets.length; i++) {
        var file = document.styleSheets[i].href;
        file = file.substring(file.lastIndexOf('/')+1);
        if (file.toLowerCase() == style.toLowerCase()) {
          return document.styleSheets[i];
        }
      }
    }
  }
//SET STYLE RULE--------------------------------------------------------------------
  function addStyleRule(sheet, selector, declarations) {
    /* This function is invoked by this.prepare().
     * This function dynamically adds the specified rule to the specified style sheet.
     */
    if (sheet.addRule) {sheet.addRule(selector,declarations);} //IE...
    else {sheet.insertRule(selector+'{'+declarations+'}');} //NON-IE...
  }
  
//PREPARE===========================================================================
  this.prepare = function(style, selectors) {
    /* This function is invoked by an external function, outside of the library.
     * This function is an interface for external scripts to access this library.
     * The function prepares the elements specified by the selectors, by adding certain style rules 
     * to them that are necessary for this library to successfully manipulate the background-image.
     */
    var selectors = getArray(selectors);
    var styleSheet = getStyleSheet(style);
    for (var i=0; i<selectors.length; i++) {
      addStyleRule(styleSheet,selectors[i],'position:relative;');
      addStyleRule(styleSheet,selectors[i]+'::after','position:absolute; top:0;right:0;bottom:0;left:0; opacity:0.0; content:"";');
    }
  };
  
//FADE BACKGROUND IMAGE=============================================================
//INIT------------------------------------------------------------------------------
  this.fade = function(style, selectors, global, opacity, endOpacity, delta) {
    /* This function is invoked by an external function, outside of the library.
     * This function is an interface for external scripts to access this library.
     * The function initiates the fading process. It first stores the appropriate 
     * set of style rules into the style rules variable, and then invokes 
     * fadeOpacity() to start the fading.
     */
    var selectors = getArray(selectors);
    var styleSheet = getStyleSheet(style);
    styleRules = styleSheet.rules ? styleSheet.rules : styleSheet.cssRules; //IE uses 'rules', NON-IE use 'cssRules'
    fadeOpacity(selectors,global,opacity,endOpacity,delta,[]);
  };
  
//FADE------------------------------------------------------------------------------
  function fadeOpacity(selectors, global, opacity, endOpacity, delta, rules) {
    /* This function is invoked by fade().
     * This function fades the background-image of the specified elements, by 
     * adding the delta to the current opacity, and then setting the opacity 
     * of all specified elements to that new value.
     */
    opacity += delta;
    if (rules.length == 0) {
      //find the css-rules that match the specified selector(s)
      for (var i=0; i<selectors.length; i++) {
        for (var j=0; j<styleRules.length; j++) {
          if (global && styleRules[j].selectorText.toLowerCase().indexOf(selectors[i].toLowerCase()+'::after')!=-1) {
            rules.push(styleRules[j]);
          } else if (styleRules[j].selectorText.toLowerCase() == selectors[i].toLowerCase()+'::after') {
            rules.push(styleRules[j]); break;
          }
        }
      }
    }
    //set the opacity of the background-image for every matched rule
    for (var i=0; i<rules.length; i++) {
      rules[i].style.opacity = opacity;
    }
    //check if the end-opacity is reached
    if (opacity < endOpacity) {
      setTimeout(function(){fadeOpacity(selectors,global,opacity,endOpacity,delta,rules);},0); //invoke itself again
    } else {
      //manually set the opacity to the end-opacity (otherwise it'll be off by a fraction)
      for (var i=0; i<rules.length; i++) {
        rules[i].style.opacity = endOpacity;
      }
      rules.length = 0;
    }
  }
};
source code of library, not a working code snippet!
(source code of library, not a working code snippet)

Interesting fact: This does not work on jsfiddle.net, because their own style sheets get mixed in with the one from the fiddle, making it impossible to determine in which sheet (based on number) the required CSS-rules reside.




回答3:


Not clear if you solved that already: For regular <img> images putting another <img> above it (display: inline-block; visibility: hidden;) and listening to the onload of the high-res image will work:

$("high-res").load(function() {
    $(this).css({visibility: "hidden", opacity: 0}).fadeIn("slow");
}

EDIT: The other way around (putting the high-res behind the low-res and then fading out the low-res) also works. But you wont get around overlaying stuff.

Fading in CSS background-images is impossible. They have no opacity value. The only thing you can do is put the contents in a <div> above an <img> and fading that in the same way.



来源:https://stackoverflow.com/questions/24194465/how-can-i-smoothly-transition-css-background-images

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!