实现文字平滑弯曲弧形效果的插件-arctext.js

扇形的文字

有时候产品大佬就是很任性,说做一个宣传页,一个类似拱门的效果,每次文字不一样,但是文字得呈现拱形状,类似上图啦。
尝试自己使用canvas画和css3的rotate旋转div,两种方法都是计算旋转角度的时候很麻烦,因为可能5个字10个字,但是得均匀地呈拱形分布,要知道让每个文字都沿着弯曲路径排布相当的复杂,于是便发现了这个好用的插件---arctext.js

它能够自动计算每个文字正确的旋转角度,并且生成对应的CSS ,其实就是基于css3和jquery,使用起来也很方便。
1.创建一个容器装文字

<h3 id="title">文字弯曲效果类似扇形拱桥状</h3>

2.引入jquery和arctext.js

<script type="text/javascript"  src="//code.jquery.com/jquery-1.8.2.min.js" ></script>
<script src="jquery.arctext.js"></script>

3.调用arctext的方法:

$(function(){
        $("#title").show().arctext({
            radius:180
        })
    })

arctext参数说明:

radius:弯曲度数,最小的值是文字长度,如果设置为-1,则显示直线。
rotate:默认true,为false则不旋转文字
dir:默认1 (1:向下弯曲 非1(-1,0,2等):向上弯曲 )
fitText:默认false,如果你想尝试使用fitText插件,设置为true,记住包装的标签需要fluid布局。

效果图完整demo:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
      #title{
            font-size: 20px;
            color: #ffe400;
            text-align: center;
        }
    </style>
</head>
<body>
    <h3 id="title">文字弯曲效果类似扇形拱桥状</h3>
<script type="text/javascript"  src="//code.jquery.com/jquery-1.8.2.min.js" ></script>
<script src="jquery.arctext.js"></script>
<script>
    $(function(){
        $("#title").arctext({
            radius:180
        })
    })
</script>
</body>
</html>

jquery.arctext.js

/**
 * Arctext.js
 * A jQuery plugin for curved text
 * http://www.codrops.com
 *
 * Copyright 2011, Pedro Botelho / Codrops
 * Free to use under the MIT license.
 *
 * Date: Mon Jan 23 2012
 */

(function( $, undefined ) {
    
    /*! 
    * FitText.js 1.0
    *
    * Copyright 2011, Dave Rupert http://daverupert.com
    * Released under the WTFPL license 
    * http://sam.zoy.org/wtfpl/
    *
    * Date: Thu May 05 14:23:00 2011 -0600
    */
    $.fn.fitText = function( kompressor, options ) {

        var settings = {
            'minFontSize' : Number.NEGATIVE_INFINITY,
            'maxFontSize' : Number.POSITIVE_INFINITY
        };

        return this.each(function() {
            var $this = $(this);              // store the object
            var compressor = kompressor || 1; // set the compressor
    
            if ( options ) { 
              $.extend( settings, options );
            }
    
            // Resizer() resizes items based on the object width divided by the compressor * 10
            var resizer = function () {
                $this.css('font-size', Math.max(Math.min($this.width() / (compressor*10), parseFloat(settings.maxFontSize)), parseFloat(settings.minFontSize)));
            };

            // Call once to set.
            resizer();

            // Call on resize. Opera debounces their resize by default. 
            $(window).resize(resizer);
        });

    };

    /*
     * Lettering plugin
     *
     * changed injector function:
     *   add &nbsp; for empty chars.
     */
    function injector(t, splitter, klass, after) {
        var a = t.text().split(splitter), inject = '', emptyclass;
        if (a.length) {
            $(a).each(function(i, item) {
                emptyclass = '';
                if(item === ' ') {
                    emptyclass = ' empty';
                    item='&nbsp;';
                }   
                inject += '<span class="'+klass+(i+1)+emptyclass+'">'+item+'</span>'+after;
            }); 
            t.empty().append(inject);
        }
    }
    
    var methods             = {
        init : function() {

            return this.each(function() {
                injector($(this), '', 'char', '');
            });

        },

        words : function() {

            return this.each(function() {
                injector($(this), ' ', 'word', ' ');
            });

        },
        
        lines : function() {

            return this.each(function() {
                var r = "eefec303079ad17405c889e092e105b0";
                // Because it's hard to split a <br/> tag consistently across browsers,
                // (*ahem* IE *ahem*), we replaces all <br/> instances with an md5 hash 
                // (of the word "split").  If you're trying to use this plugin on that 
                // md5 hash string, it will fail because you're being ridiculous.
                injector($(this).children("br").replaceWith(r).end(), r, 'line', '');
            });

        }
    };

    $.fn.lettering          = function( method ) {
        // Method calling logic
        if ( method && methods[method] ) {
            return methods[ method ].apply( this, [].slice.call( arguments, 1 ));
        } else if ( method === 'letters' || ! method ) {
            return methods.init.apply( this, [].slice.call( arguments, 0 ) ); // always pass an array
        }
        $.error( 'Method ' +  method + ' does not exist on jQuery.lettering' );
        return this;
    };
    
    /*
     * Arctext object.
     */
    $.Arctext               = function( options, element ) {
    
        this.$el    = $( element );
        this._init( options );
        
    };
    
    $.Arctext.defaults      = {
        radius  : 0,    // the minimum value allowed is half of the word length. if set to -1, the word will be straight.
        dir     : 1,    // 1: curve is down, -1: curve is up.
        rotate  : true, // if true each letter will be rotated.
        fitText : false // if you wanna try out the fitText plugin (http://fittextjs.com/) set this to true. Don't forget the wrapper should be fluid.
    };
    
    $.Arctext.prototype     = {
        _init               : function( options ) {
            
            this.options        = $.extend( true, {}, $.Arctext.defaults, options );
            
            // apply the lettering plugin.
            this._applyLettering();
            
            this.$el.data( 'arctext', true );
            
            // calculate values
            this._calc();
            
            // apply transformation.
            this._rotateWord();
            
            // load the events
            this._loadEvents();
            
        },
        _applyLettering     : function() {
        
            this.$el.lettering();
            
            if( this.options.fitText )
                this.$el.fitText();
            
            this.$letters   = this.$el.find('span').css('display', 'inline-block');
        
        },
        _calc               : function() {
            
            if( this.options.radius === -1 )
                return false;
            
            // calculate word / arc sizes & distances.
            this._calcBase();
            
            // get final values for each letter.
            this._calcLetters();
        
        },
        _calcBase           : function() {
            
            // total word width (sum of letters widths)
            this.dtWord     = 0;
            
            var _self       = this;
            
            this.$letters.each( function(i) {
                                
                var $letter         = $(this),
                    letterWidth     = $letter.outerWidth( true );
                
                _self.dtWord += letterWidth;
                
                // save the center point of each letter:
                $letter.data( 'center', _self.dtWord - letterWidth / 2 );
                
            });
            
            // the middle point of the word.
            var centerWord = this.dtWord / 2;
            
            // check radius : the minimum value allowed is half of the word length.
            if( this.options.radius < centerWord )
                this.options.radius = centerWord;
            
            // total arc segment length, where the letters will be placed.
            this.dtArcBase  = this.dtWord;
            
            // calculate the arc (length) that goes from the beginning of the first letter (x=0) to the end of the last letter (x=this.dtWord).
            // first lets calculate the angle for the triangle with base = this.dtArcBase and the other two sides = radius.
            var angle       = 2 * Math.asin( this.dtArcBase / ( 2 * this.options.radius ) );
            
            // given the formula: L(ength) = R(adius) x A(ngle), we calculate our arc length.
            this.dtArc      = this.options.radius * angle;
            
        },
        _calcLetters        : function() {
            
            var _self       = this,
                iteratorX   = 0;
                
            this.$letters.each( function(i) {
                    
                var $letter         = $(this),
                    // calculate each letter's semi arc given the percentage of each letter on the original word.
                    dtArcLetter     = ( $letter.outerWidth( true ) / _self.dtWord ) * _self.dtArc,
                    // angle for the dtArcLetter given our radius.
                    beta            = dtArcLetter / _self.options.radius,
                    // distance from the middle point of the semi arc's chord to the center of the circle.
                    // this is going to be the place where the letter will be positioned.
                    h               = _self.options.radius * ( Math.cos( beta / 2 ) ),
                    // angle formed by the x-axis and the left most point of the chord.
                    alpha           = Math.acos( ( _self.dtWord / 2 - iteratorX ) / _self.options.radius ),
                    // angle formed by the x-axis and the right most point of the chord.
                    theta           = alpha + beta / 2,
                    // distances of the sides of the triangle formed by h and the orthogonal to the x-axis.
                    x               = Math.cos( theta ) * h,
                    y               = Math.sin( theta ) * h,
                    // the value for the coordinate x of the middle point of the chord.
                    xpos            = iteratorX + Math.abs( _self.dtWord / 2 - x - iteratorX ),
                    // finally, calculate how much to translate each letter, given its center point.
                    // also calculate the angle to rotate the letter accordingly.
                    xval    = 0| xpos - $letter.data( 'center' ),
                    yval    = 0| _self.options.radius - y,
                    angle   = ( _self.options.rotate ) ? 0| -Math.asin( x / _self.options.radius ) * ( 180 / Math.PI ) : 0;
                
                // the iteratorX will be positioned on the second point of each semi arc
                iteratorX = 2 * xpos - iteratorX;
                
                // save these values
                $letter.data({
                    x   : xval,
                    y   : ( _self.options.dir === 1 ) ? yval : -yval,
                    a   : ( _self.options.dir === 1 ) ? angle : -angle
                });
                    
            });
        
        },
        _rotateWord         : function( animation ) {
            
            if( !this.$el.data('arctext') ) return false;
            
            var _self = this;
            
            this.$letters.each( function(i) {
                
                var $letter         = $(this),
                    transformation  = ( _self.options.radius === -1 ) ? 'none' : 'translateX(' + $letter.data('x') + 'px) translateY(' + $letter.data('y') + 'px) rotate(' + $letter.data('a') + 'deg)',
                    transition      = ( animation ) ? 'all ' + ( animation.speed || 0 ) + 'ms ' + ( animation.easing || 'linear' ) : 'none';
                
                $letter.css({
                    '-webkit-transition' : transition,
                    '-moz-transition' : transition,
                    '-o-transition' : transition,
                    '-ms-transition' : transition,
                    'transition' : transition
                })
                .css({
                    '-webkit-transform' : transformation,
                    '-moz-transform' : transformation,
                    '-o-transform' : transformation,
                    '-ms-transform' : transformation,
                    'transform' : transformation
                });
            
            });
            
        },
        _loadEvents         : function() {
            
            if( this.options.fitText ) {
            
                var _self = this;
                
                $(window).on( 'resize.arctext', function() {
                    
                    _self._calc();
                    
                    // apply transformation.
                    _self._rotateWord();
                    
                });
            
            }
        
        },
        set                 : function( opts ) {
            
            if( !opts.radius &&  
                !opts.dir &&
                opts.rotate === 'undefined' ) {
                    return false;
            }
            
            this.options.radius = opts.radius || this.options.radius;
            this.options.dir    = opts.dir || this.options.dir;
            
            if( opts.rotate !== undefined ) {
                this.options.rotate = opts.rotate;
            }   
            
            this._calc();
            
            this._rotateWord( opts.animation );
            
        },
        destroy             : function() {
            
            this.options.radius = -1;
            this._rotateWord();
            this.$letters.removeData('x y a center');
            this.$el.removeData('arctext');
            $(window).off('.arctext');
            
        }
    };
    
    var logError            = function( message ) {
        if ( this.console ) {
            console.error( message );
        }
    };
    
    $.fn.arctext            = function( options ) {
    
        if ( typeof options === 'string' ) {
            
            var args = Array.prototype.slice.call( arguments, 1 );
            
            this.each(function() {
            
                var instance = $.data( this, 'arctext' );
                
                if ( !instance ) {
                    logError( "cannot call methods on arctext prior to initialization; " +
                    "attempted to call method '" + options + "'" );
                    return;
                }
                
                if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
                    logError( "no such method '" + options + "' for arctext instance" );
                    return;
                }
                
                instance[ options ].apply( instance, args );
            
            });
        
        } 
        else {
        
            this.each(function() {
            
                var instance = $.data( this, 'arctext' );
                if ( !instance ) {
                    $.data( this, 'arctext', new $.Arctext( options, this ) );
                }
            });
        
        }
        
        return this;
        
    };
    
})( jQuery );

原文作者技术博客:https://www.jianshu.com/u/ac4daaeecdfe
95后前端妹子一枚,爱阅读,爱交友,将工作中遇到的问题记录在这里,希望给每一个看到的你能带来一点帮助。
欢迎留言交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容