继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

HTML5自定义元素播放焦点图动画

一只名叫tom的猫
关注TA
已关注
手记 484
粉丝 62
获赞 330

这是一款基于HTML5的焦点图动画,它和其他焦点图不同的地方在于,它播放的不仅仅是图片,而是可以自定义控制的网页元素。它不仅在图片间切换有过渡动画效果,而且在切换时图片中的元素也将出现动画效果,比如图中的文字移动、打散、重新组合等。这款HTML5动画图片播放器算得上是高端大气上档次。

在线演示源码下载

HTML代码

<div class="parallax-bg" id="slider-wrap">
	<div class="slider parallax-bg" id="slider">
           <div class="slider-sections sandbox"> 
		<section class="first">
                     <img alt="Kendo UI" src="images/home-banner-1.png"/>
                     <div class="text">
                          <h2>SmartSite Ver 2.2<br />智能网站管理系统 </h2>
                          <p class="copy">采用前后台完全分离技术,通过标签(支持标签循环嵌套、判断标签、自定义标签、文件循环嵌套等)加模板技术.全站生成纯静态页。</p>
                          <p class="button"><a href="#" onclick="_gaq.push(['_trackPageview', '#']);">Download</a> <a class="dimmed" onclick="_gaq.push(['_trackPageview', '#']);" href="#">Learn More</a></p>
                     </div>
                </section>
		<section>
                     <img src="images/dataviz-home-image-q2.png" alt="Kendo UI" />
                     <div class="text" style="padding-top: 10px;">
                          <h2>企业网站管理系统</h2>
                          <p class="copy">单页面、单页面索引、新闻、产品展示、下载、友情链接、网上商城,在线支付、配送、支付方式管理、广告等模块。</p>
                          <p class="button"><a href="#" onclick="_gaq.push(['_trackPageview', '#']);">Download</a> <a class="dimmed" onclick="_gaq.push(['_trackPageview', '#']);" href="#">Learn More</a></p>
                     </div>
                </section>
		<section>
                     <img src="images/home_banner_web-q2.png" alt="Kendo UI" />
                     <div class="text">
                          <h2>智能移动网站管理系统</h2>
                          <p class="copy">基于jquery.Mobile、HTML5技术框架,前后台完全分离,采用标签加模板技术,全站生成纯静态页。</p>
                          <p class="button"><a href="#" onclick="_gaq.push(['_trackPageview', '#']);">Download</a> <a class="dimmed" onclick="_gaq.push(['_trackPageview', '#']);" href="#">Learn More</a></p>
                     </div>
                </section>
           </div>
      </div>
      <a class="slider-prev" href="javascript: void(0)">?</a> <a class="slider-next" href="javascript: void(0)">?</a></div>

CSS代码:

这里列出的是这个焦点图相关的核心CSS代码。

.slider section {display: none;}.slider section.first {display: block;}.slider-sections, .slider-sections section {width: 861px; height: 335px;}.slider-sections {margin: 0 auto; position: relative;}.slider-sections section {position: absolute; top: 0; left: 0px; }.header-content h2 {	font:400 32px/1.2 "microsoft yahei", Tahoma, arial, sans-serif;	color: #fff;	margin: 0 0 26px;
}.header-content p {    margin: 0 0 30px;
}.header-content .centered-content{	padding-top: 30px;	padding-bottom: 10px;
}.button {	float: left;	width: auto !important;	list-style: none;
}	.button a,	.button button,	.button input { /* Standard black button */
	  font-size: 15px;	  /*font-family: 'lucida sans',arial,helvetica,sans-serif;*/
	  line-height: 18px;		color: #fff !important;	  text-decoration: none;	  padding: 5px 14px 6px 13px;		display: block;		width: auto;		position: relative;		z-index: 2;	    border: none;        -moz-border-radius: 3px;		border-radius: 3px;		cursor: pointer;		background: #313131; /* Old browsers */
		background: -moz-linear-gradient(top, #313131 0%, #222222 100%); /* FF3.6+ */
		background: -webkit-linear-gradient(top, #313131 0%,#222222 100%); /* Chrome10+,Safari5.1+ */
		background: -o-linear-gradient(top, #313131 0%,#222222 100%); /* Opera11.10+ */
		background: -ms-linear-gradient(top, #313131 0%,#222222 100%); /* IE10+ */
		background: linear-gradient(top, #313131 0%,#222222 100%); /* W3C */

		-webkit-transition: all 0.3s ease;		-moz-transition: all 0.3s ease;		-o-transition: all 0.3s ease;		transition: all  0.3s ease;
	}	.button a:hover,	.button input:hover,	.button button:hover,	.button a:focus,	.button input:focus,	.button button:focus
	{	  background: #464646; /* Old browsers */
		background: -moz-linear-gradient(top, #464646 0%, #393939 100%); /* FF3.6+ */
		background: -webkit-linear-gradient(top, #464646 0%,#393939 100%); /* Chrome10+,Safari5.1+ */
		background: -o-linear-gradient(top, #464646 0%,#393939 100%); /* Opera11.10+ */
		background: -ms-linear-gradient(top, #464646 0%,#393939 100%); /* IE10+ */
		background: linear-gradient(top, #464646 0%,#393939 100%); /* W3C */
    }	header .header-content .button a,	#content .button a:hover
	{	    text-decoration: none;
    }.header-content .beta-ribbons {    position: absolute; 
    height: 120px;    width: 85px;    text-indent: -200px;    overflow: hidden;    background: url(../images/kendo-ribbons.png) no-repeat 0 0;
}.header-content .beta-ribbons.ribbon-1 {    background-position: -170px 0;    top: -3px;    right: -145px;
}.header-content p.copy .beta-ribbons.ribbon-1 {    top: -135px;    left: 900px;
}.header-content .beta-ribbons.ribbon-4 {    background-position: -255px 0;    left: -62px;    top: -30px;    z-index: 10;		text-indent: -2000px;
}

JavaScript代码:

下面是这个焦点图插件的代码。

/**
* @author Alexander Farkas
* v. 1.22
*/(function ($){    if (!document.defaultView || !document.defaultView.getComputedStyle)
    { // IE6-IE8
        var oldCurCSS = $.curCSS;
        $.curCSS = function (elem, name, force)        {            if (name === 'background-position')
            {
                name = 'backgroundPosition';
            }            if (name !== 'backgroundPosition' || !elem.currentStyle || elem.currentStyle[name])
            {                return oldCurCSS.apply(this, arguments);
            }            var style = elem.style;            if (!force && style && style[name])
            {                return style[name];
            }            return oldCurCSS(elem, 'backgroundPositionX', force) + ' ' + oldCurCSS(elem, 'backgroundPositionY', force);
        };
    }    var oldAnim = $.fn.animate;
    $.fn.animate = function (prop)    {        if ('background-position' in prop)
        {
            prop.backgroundPosition = prop['background-position'];            delete prop['background-position'];
        }        if ('backgroundPosition' in prop)
        {
            prop.backgroundPosition = '(' + prop.backgroundPosition;
        }        return oldAnim.apply(this, arguments);
    };    function toArray(strg)    {
        strg = strg.replace(/left|top/g, '0px');
        strg = strg.replace(/right|bottom/g, '100%');
        strg = strg.replace(/([0-9\.]+)(\s|\)|$)/g, "$1px$2");        var res = strg.match(/(-?[0-9\.]+)(px|\%|em|pt)\s(-?[0-9\.]+)(px|\%|em|pt)/);        return [parseFloat(res[1], 10), res[2], parseFloat(res[3], 10), res[3]];
    }

    $.fx.step.backgroundPosition = function (fx)    {        if (!fx.bgPosReady)
        {            var start = $.curCSS(fx.elem, 'backgroundPosition');            if (!start)
            {//FF2 no inline-style fallback
                start = '0px 0px';
            }

            start = toArray(start);
            fx.start = [start[0], start[2]];            var end = toArray(fx.end);
            fx.end = [end[0], end[2]];

            fx.unit = [end[1], end[3]];
            fx.bgPosReady = true;
        }        //return;
        var nowPosX = [];
        nowPosX[0] = ((fx.end[0] - fx.start[0]) * fx.pos) + fx.start[0] + fx.unit[0];
        nowPosX[1] = ((fx.end[1] - fx.start[1]) * fx.pos) + fx.start[1] + fx.unit[1];
        fx.elem.style.backgroundPosition = nowPosX[0] + ' ' + nowPosX[1];

    };
})(jQuery);/*
tlrkSlider

  example usage:

    $("#slider").tlrkSlider({
      autoStart: false,
      elements: {
        "img": {delay: 10},
        "h2": {delay: 500},
        ".copy": {delay: 800},
        ".button": {delay: 1000}
      }
    });

  to go to a specific frame:
    $("#slider").tlrkSlider("go", position);
    "position" can have one of the following values:
    "next", "prev", "first", "last", "+1", "-1" or a numeric value

  to start/stop the automatic loop:
    $("#slider").tlrkSlider("start");
    $("#slider").tlrkSlider("stop");

  to change the delay between automatic transitions:
    $("#slider").tlrkSlider("option", "delayAnimation", 1000);

  to change any option:
    $("#slider").tlrkSlider("option", option_name, option_value);

  Changing the "elements" object is not tested.

  Changing the following options: "navigation", "navigationClass", "framesSelector", "autoStart" won't have any effect for now.
  They are used only during the initialization.

  $("#slider").data("tlrkSlider") will return the plugin instance and the methods/properties can be accessed from there.

  The plugin contructor defaults are accessable through TlrkSlider.defaults

  The function that actually sweep the elements in/out can be overriden from
    TlrkSlider.prototype._animationIn and TlrkSlider.prototype._animationOut

    See sweepIn/sweepOut 

 */;(function( $, window, document, undefined ){  // utility function that generates the "dots" navigation
  function generateNavigation($el, count, config) {    var i, html = "",
        width = count * 24;

    html += "<ol class='" + config.navigationClass + "' style='margin-left: -" + width/2 + "px; width: " + width + "px'>";    for (i = 0; i < count; i++) {
      html += "<li><a " + (i === 0 ? "class='selected'" : "" ) + " href='#" + (i) + "'>slide</a></li>";
    }
    html += "</ol>";

    $el.append(html);
  }  function sweepOut($el, windowWidth) {    var dfr = $.Deferred(),
        pos = $el.position(),
        width = $el.width(),
        delta, final,
        options = $el.data("tlrkAnimOptions");

    windowWidth = windowWidth || $(window).width(); // check if the windowWidth is passed, if not - get it

    delta = windowWidth - pos.left;
    final = -(delta);

    setTimeout(function(){
      $el.animate({left: final, opacity: "toggle"}, options.speed, options.easing, function(){
        dfr.resolve();
      });
    }, options.delay);    return dfr.promise();
  }  function sweepIn($el, windowWidth, frameLeft) {    var dfr = $.Deferred(),
        options = $el.data("tlrkAnimOptions"),
        positionData = $el.data("tlrkOriginalPos"),
        final = positionData.position.left,
        rightEdge;

    windowWidth = windowWidth || $(window).width(); // check if the windowWidth is passed, if not - get it

    $el.css({opacity: 0, display: "block"}); // move it outside the right edge of the screen
    $el.css("left", function(current){      return current + windowWidth - frameLeft;
    });

    setTimeout(function(){
      $el.animate({left: final, opacity: 1}, options.speed, options.easing, function(){
        dfr.resolve();
      });
    }, options.delay);    return dfr.promise();
  }  // two pass function that first iterates all the elements and gets their position/width/height
  // and then sets their position to absolute
  function absolutize($elements) {    // todo - move it to separate function and do it just once
    // gather the original position/dimension data for all elements
    $elements.each(function(){      var $t = $(this);      if ($t.data("tlrkOriginalPos")) return

      $t.data("tlrkOriginalPos", {
        position: $t.position(),
        width: $t.width(),
        height: $t.height(),
        css_pos: $t.css("position"),
        css_left: $t.css("left"),
        css_top: $t.css("top"),
        css_width: $t.css("width") || "auto",
        css_height: $t.css("height") || "auto"
      });

    });    // set the absolute position
    $elements.each(function(){      var $t = $(this),
          opos = $t.data("tlrkOriginalPos");

      $t.css({
        position: "absolute",
        left: opos.position.left,
        top: opos.position.top,
        width: opos.width,
        height: opos.height
      });
    });
  }  function restoreFrameElements($elements) {
    $elements.each(function(){      var $t = $(this),
          opos = $t.data("tlrkOriginalPos");      if (!opos) return

      $t.css({
        position: opos.css_pos,
        left: opos.css_left,
        top: opos.css_top,
        width: opos.css_width,
        height: opos.css_height
      });
    });

  }  var TlrkSlider = function( elem, options ){      this.elem = elem;      this.$elem = $(elem);      this.options = options;
    };  // the plugin prototype
  TlrkSlider.prototype = {
    defaults: {

      defaultElementOptions: {
        speed: 1200,
        easing: "easeInOutBack",        // interval before the element starts moving when the fadeIn/Out functions are called
        // it's a good idea to give different delays for the different elements
        // if all have the same delay they'll start moving all together
        delay: 100 
      },      // dispose elements are these that are not included in the elements object
      // but affect the document flow and will be fadedIn/Out
      disposeDelay: 100, // delay for the dispose elements
      disposeSpeed: 1000, // how quickly they'll fadeOut/In

      delayBetweenTransition: 1000, // time between starting fadeOut and fadeIn
      delayAnimation: 7000, // time between auto changing the current frame

      loop: true, // if true when clicking next on the last frame the slider jumps to the first one

      autoStart: true, // start the automatic looping through the frames on init

      framesSelector: "section", // selector for the frames inside the slider

      elements: {        "p": {delay: 100, speed: 1000, easing: "easeInOutBack"}
      },

      navigation: true, // the dots navigation on the bottom
      navigationClass: "slider-nav",      // callbacks
      // another way to "catch" these events is with
      // $(-slider-element-).bind("animationStart")
      animationStart: null,
      animationEnd: null
    },

    init: function() {      var c, e, element, $element,
          that = this,
          $firstFrame;

      c = this.config = $.extend({}, this.defaults, this.options);      this.elem.style.position = "relative"; // make the wrapping element relative

      // basics
      this.$frames = this.$elem.find(c.framesSelector);      this.framesCount = this.$frames.length;      this.currentFrame = 0;      this.queue = [];      this._$elementsByFrame = {};      this._$disposeElementsByFrame = {};      for (i = 0; i < this.framesCount; i++) {        this._$elementsByFrame[i] = this._getFrameElements(i); // cache the $elements by frame
        this._$disposeElementsByFrame[i] = this._getDisposeFrameElements(i); // cache the rest of the tree for each frame
      }      if (c.navigation) {
        generateNavigation(this.$elem, this.framesCount, c);        this.$navigation = this.$elem.find("."+c.navigationClass);
      }      // bindings
      this.$elem.find(".slider-nav").delegate("a", "click", function(e){        var frame = this.getAttribute("href").split("#")[1];
        that.go.call(that, frame);        return false;
      });      this.$elem // internal bindings for the callbacks
        .bind("animationStart", function(){          if ($.isFunction(c.animationStart)) {c.animationStart.apply(that, arguments);}
        })
        .bind("animationEnd", function(){          if ($.isFunction(c.animationEnd)) {c.animationEnd.apply(that, arguments);}
        })
      ;      // start animation?
      if (c.autoStart) {        this.start();
      } else {        this.running = false;
      }      return this;
    },

    start: function(instant) {      var that = this;      if (this.timer) { // we'll clear the current timer
        window.clearTimeout(this.timer);
      }      this.running = true;      if (instant) {
        that.nextFrame();
      } else {        this.timer = window.setTimeout(function(){ that.nextFrame.call(that) }, that.config.delayAnimation);
      }
    },

    stop: function() {      if (!this.running) return; // we are not running

      this.running = false;      window.clearTimeout(this.timer);
    },    // main function for changing frames
    selectFrame: function(frame, dfr) {      var c = this.config, // shorthand for the config
          that = this,
          dfr = dfr || $.Deferred(),
          dFadeIn = $.Deferred(),
          dFadeOut = $.Deferred();      if (isNaN(frame) || frame < 0 || frame > this.framesCount || frame === this.currentFrame) {
        dfr.reject();        return dfr.promise();
      }      // clear the animation loop interval if the animation is running
      if (this.running && this.timer) { 
        window.clearTimeout(this.timer);
      }      // check if we are currenly running an animation.
      if (this.animated && this.queue.length > 0) {        // wait for the last item in the queue to finish
        this.queue[this.queue.length-1].done(function(){
          that.selectFrame(frame, dfr); // and call again the selectFrame
        })        return dfr.promise();
      }      this.animated = true;      this.$elem.trigger("animationStart", [this, frame]);      this.queue.push(dfr);      // fade the frames
      dFadeOut = this._fadeOutFrame(this.currentFrame);      // hide the fadetout frame
      dFadeOut.done(function(){
        that.$frames.eq(that.currentFrame).hide();
      });      window.setTimeout(function(){ // then wait delayBetweenTransition and fadeIn the new frame
        dFadeIn = that._fadeInFrame.call(that, frame).done(function(){          // when both the fadeIn and fadeOut are done we'll resolve the selectFrame promise
          $.when(dFadeOut, dFadeIn).done(function(){
            that.animated = false;
            that.queue.shift();
            that.$elem.trigger("animationEnd", [that]);
            that.currentFrame = frame;
            dfr.resolve();
          });
        });
      }, c.delayBetweenTransition);      // navigation html change
      if (this.config.navigation) {        this.$navigation.find(".selected").removeClass("selected").end()
          .find("a").eq(frame).addClass("selected");
      }

      dfr.done(function(){ // we'll resume the loop animation after the transitions are done
        if (that.running) {
          that.start();
        }
      });      return dfr.promise();
    },

    _fadeFrame: function(frame, callback, direction) {      var dfr = $.Deferred(),
          $frame = this.$frames.eq(frame),
          $elements = this._$elementsByFrame[frame],
          windowWidth = $(window).width(), // cache it before the animations, so we don't have to check it for each element 
          i, len,
          that = this,
          elementAnimations = [],
          $disposeElements = this._$disposeElementsByFrame[frame],
          $affectedElements,
          frameLeft = $frame.offset().left;

      direction = direction || "out";      if (!$.isFunction(callback)) return; // do nothing if there's no callback passed

      $affectedElements = $elements.add($disposeElements);      // position absolute the animation and dispose elements
      absolutize($affectedElements);      // toggle the dispose elements
      if ($disposeElements.length > 0) {        window.setTimeout(function(){
          $disposeElements[direction === "out" ? "fadeOut" : "fadeIn"](that.config.disposeSpeed);
        }, this.config.disposeDelay);
      }      // invoke the callback for each element
      // the callback must return a promise
      $elements.each(function(){
        elementAnimations.push( callback.call(that, $(this), windowWidth, frameLeft) );
      });      // wait for all the elements to finish their animation
      $.when.apply(this, elementAnimations).done(function(){        //restoreFrameElements($affectedElements); // and restore the elements' position
        dfr.resolve(); // resolve the fade function
      });      return dfr.promise();
    },

    _fadeOutFrame: function(frame) {      var dfr = $.Deferred(),
          $frame = this.$frames.eq(frame),
          $disposeElements = this._$disposeElementsByFrame[frame];      this._fadeFrame(frame, this._animationOut, "out").done(function(){
        dfr.resolve();
      })      return dfr.promise();
    },

    _fadeInFrame: function(frame) {      var dfr = $.Deferred(),
          $frame = this.$frames.eq(frame),
          $elements = this._$elementsByFrame[frame];      this._restoreFrame(frame);

      $frame.show();      this._fadeFrame(frame, this._animationIn, "in").done(function(){
        dfr.resolve();
      });      return dfr.promise();
    },

    _restoreFrame: function(frame){      if (!frame) return
      restoreFrameElements( this._$elementsByFrame[frame].add(this._$disposeElementsByFrame[frame]) );
    },

    nextFrame: function() {      var frame = this.currentFrame+1,
          dfr = $.Deferred();      if (frame > this.framesCount-1) {        if (this.config.loop) {
          frame = 0;
        } else {
          dfr.reject();
        }
      };      this.selectFrame(frame).done(function(){
        dfr.resolve();
      });      return dfr.promise();
    },

    prevFrame: function() {      var frame = this.currentFrame-1,
          dfr = $.Deferred();      if (frame < 0) {        if (this.config.loop) {
          frame = this.framesCount-1;
        } else {
          dfr.reject();          return dfr.promise();
        }
      }      this.selectFrame(frame).done(function(){
        dfr.resolve();
      });      return dfr.promise();
    },

    go: function(str) { // shorthand
      switch (str) {        case "next":        case "+1":          this.nextFrame();          break;        case "prev":        case "-1":          this.prevFrame();          break;        case "first":          this.selectFrame(0);          break;        case "last":          this.selectFrame(this.framesCount-1);          break;        default:          if (isNaN(str)) return;          this.selectFrame(Number(str));
      }
    },    // returns jquery collection of animation elements
    _getFrameElements: function(frame) { 
      var $frame = this.$frames.eq(frame),
          elements = this.config.elements,
          e, elementOptions,
          $found, $frameElements = $([]);      for (e in elements) {
        elementOptions = elements[e];
        $found = $frame.find(e);
        $found.addClass("t-frame-element").data("tlrkAnimOptions", $.extend({}, this.defaults.defaultElementOptions, elementOptions ));
        $frameElements = $frameElements.add($found);
      }      return $frameElements;
    },    // returns jquery collection of elements that have to be faded out
    // i.e. elements on the same level as the animation elements
    // that doesn't contain other animation elements
    _getDisposeFrameElements: function(frame) {      var $disposeElements = $([]),
          $frame = this.$frames.eq(frame),
          $elements = this._$elementsByFrame[frame];

      $elements.each(function(){        var $t = $(this),
            $siblings = $t.siblings().not(".t-frame-element");

        $siblings.each(function(){          var $t = $(this);          // check if the node is not already marked and doesn't contains other frame elements
          if (!$t.hasClass("t-frame-dispose") && $t.find(".t-frame-element").length === 0) {
            $t.addClass("t-frame-dispose");
            $disposeElements = $disposeElements.add($t);
          }
        });

      });      return $disposeElements;
    },    // expose the internal animationIn/Out functions that are called for each element in the frame
    // two arguments are passed - the $element which have to be animated and the window width
    _animationIn: sweepIn,
    _animationOut: sweepOut

  }

  TlrkSlider.defaults = TlrkSlider.prototype.defaults;

  $.fn.tlrkSlider = function(options) {    var otherArgs = Array.prototype.slice.call(arguments, 1);    return this.each(function() {      var $el = $(this),
          pluginData = $el.data("tlrkSlider");      if (!pluginData) { // check if the slider is already attached
        pluginData = new TlrkSlider(this, options).init();
        $el.data("tlrkSlider", pluginData);        return;
      }      //change the options or call a method
      if (typeof options === "string") {        // setting / getting option(s)
        if (options === "option") {          if (typeof otherArgs[0] === "string" && typeof otherArgs[1] !== "undefined") { // set an option value
            pluginData.config[otherArgs[0]] = otherArgs[1]; 
          }          if (typeof otherArgs[0] === "object") { // extend the config with new options
            pluginData.config = $.extend(pluginData.config, otherArgs[0]);
          }

        } else { // call a method?
          try {
            pluginData[options].apply(pluginData, otherArgs);
          } catch(ex) {            throw "Error calling a plugin method (" + ex + ")";
          }
        }
      }
    });
  };  window.TlrkSlider = TlrkSlider;

})( jQuery, window , document );

下面是页面调用的JS代码:

$(document).ready(function(){  var $backgrounds = $(".header-content").find(".parallax-bg"),
      LAYER_OFFSET = 30,
      PRLX_SPEED = 1500,
      $slider;

  $slider = $("#slider").tlrkSlider({
    autoStart: true,
    animationStart: function(ev, slider, step){      var max_steps = this.framesCount; 
      $backgrounds.each(function(idx, el){        var pos = (step * (100 / max_steps)) + (LAYER_OFFSET * idx);
        $(this).animate({"backgroundPosition": pos + "% 0"}, PRLX_SPEED);
      });
    },
    elements: {      "img": {delay: 10},      "h2": {delay: 500},      ".copy": {delay: 800},      ".button": {delay: 1000}
    }
  });

  $(".header-content")
    .hover(      function(){$(this).find(".slider-prev, .slider-next").show();},      function(){$(this).find(".slider-prev, .slider-next").hide();}
    )
    .find(".slider-prev").click(function(){$slider.tlrkSlider("go", "prev"); return false; }).end()
    .find(".slider-next").click(function(){$slider.tlrkSlider("go", "next"); return false; });

});

在线演示     源码下载

本文链接:http://www.codeceo.com/article/html5-element-image-player.html
本文作者:码农网 – 小峰


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP